Compare commits

..

224 Commits

Author SHA1 Message Date
Paulus Schoutsen
2c440976aa use heading property 2021-09-30 09:31:27 -07:00
Joakim Sørensen
abbfe7200a Fix supervisor dev translations (#10113) 2021-09-30 09:01:36 -07:00
Paulus Schoutsen
419942112b Fix Lit lint warnings (#10112) 2021-09-30 08:46:03 -07:00
Bram Kragten
597d4a0426 Use const enums where possible (#10110) 2021-09-30 07:44:28 -07:00
Bram Kragten
e023d60be7 exclude a bunch of polyfill locales (#10111) 2021-09-30 07:43:46 -07:00
Bram Kragten
41a7b42037 Bumped version to 20210930.0 2021-09-30 12:41:35 +02:00
Bram Kragten
2936865c55 Bump typescript, lint, prettier (#10108) 2021-09-30 12:39:03 +02:00
smonesi
ff2bf1f3c1 Local images flagged as already loaded to avoid flickering/slow-down (#10086) 2021-09-30 08:14:08 +00:00
Bram Kragten
1bccbd4173 Use browser default time and number formatting with polyfills if needed (#9481)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-09-29 23:34:52 +00:00
Bram Kragten
d7f00df391 Bump yarn to v3 (#10104) 2021-09-29 15:57:50 -07:00
Bram Kragten
22f88c59c7 Bump lit and mwc (#10103) 2021-09-29 15:09:43 -07:00
Bram Kragten
8721776839 Add migration wizard for zwave -> zwave_js (#10097) 2021-09-29 08:55:20 -07:00
craiggenner
a89da0dac0 add 'allow-download' to iframe sandbox for chrome (#9490) 2021-09-29 14:40:26 +02:00
Bram Kragten
e4b4dc4ae9 Allow gas to be in kWh (#10075)
* Allow gas to be in kWh

* Extract some gas unit helpers

* Forgot to save a refactor

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-09-29 10:33:48 +02:00
Bram Kragten
b26c44b2b9 Add S2 support to Z wave JS (#10090)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: kpine <keith.pine@gmail.com>
2021-09-28 15:37:32 -07:00
Bram Kragten
68095417b9 Fix tooltip and click action on device energy graph (#10094) 2021-09-28 15:21:47 -07:00
Joakim Sørensen
b6344eb6e8 Fix link to os_agent (#10093) 2021-09-28 09:09:27 +02:00
Paulus Schoutsen
224302cfef Simplify remote connection preferences (#10071)
* Simplify remote connection preferences

* Remove unused CSS and strings
2021-09-27 22:28:43 -07:00
Bram Kragten
abc4816888 Only show entities in energy with energy device class (#10076) 2021-09-27 17:56:56 -07:00
Bram Kragten
21e14bd644 Fix energy device graph when multiple entities have same name (#10092) 2021-09-27 17:55:46 -07:00
Bram Kragten
a89caccd32 Statistics dev tools (#10074)
* Statistics dev tools

* Show all statistics

* Update src/data/history.ts

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Update history.ts

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-09-27 22:03:57 +02:00
Joakim Sørensen
03dc3e52b7 Add missing unsupported links (#10080) 2021-09-23 15:08:51 +02:00
Paulus Schoutsen
f04be8efa6 Hide hassio integration during onboarding (#10079) 2021-09-23 08:12:04 +02:00
Paulus Schoutsen
2c32f6bcb3 Bumped version to 20210922.0 2021-09-22 14:20:23 -07:00
Joakim Sørensen
a3a08ff5c7 Add dialog to trigger moving datadisk (#10048)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-22 13:37:21 -07:00
Jefferson Bledsoe
ea51186767 Update delete dashboard dialog to be more descriptive (#10051)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2021-09-22 11:38:21 +02:00
Philip Allgaier
49494c572b Fix card deletion dialog buttons out of view (#9992)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-20 16:32:41 +02:00
Philip Allgaier
fcac3fa164 Convert suggest-card dialog to ha-dialog (#10000)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-20 16:32:13 +02:00
Jaroslav Hanslík
9c1153ef37 Unified alarm panel icons everywhere (#10021) 2021-09-20 11:06:31 +00:00
Philip Allgaier
0adc4b33ef Align "option" wording and show user-friendly option index in trace timeline (#10059) 2021-09-20 12:59:30 +02:00
Will Adler
c0f3215340 Clarify carbon-consumed energy dashboard card tooltip (#10053) 2021-09-20 12:52:20 +02:00
Will Adler
bab1e6a95f Revise solar-consumed energy dashboard card tooltip, fix typos (#10052) 2021-09-20 12:51:47 +02:00
Erik Montnemery
53b26a43c0 Update translation strings for energy validator (#10037)
* Update translation strings for energy validator

* Update src/translations/en.json

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Update en.json

* Update src/translations/en.json

Co-authored-by: Philip Allgaier <philip.allgaier@gmx.de>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Philip Allgaier <philip.allgaier@gmx.de>
2021-09-20 12:38:16 +02:00
Erik Montnemery
2240d019f5 Specify period when fetching statistics (#10040)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-16 11:15:23 +02:00
Philip Allgaier
cb11c6b3ea Check for null action nodes before rendering (#10017) 2021-09-14 14:20:09 -07:00
Jaroslav Hanslík
5893559951 New icon for "on" state of smoke device (#10013) 2021-09-13 12:50:57 +02:00
Paulus Schoutsen
8408d25cef Clean up ha state label badge (#10020) 2021-09-12 10:00:37 +02:00
Paulus Schoutsen
1ac2ffcf02 Bumped version to 20210911.0 2021-09-11 11:57:48 -07:00
Jaroslav Hanslík
6b6c38c2c8 Better icon for alarm panel state "armed night" (#10012) 2021-09-11 11:53:38 -07:00
Joakim Sørensen
e55df73a91 Remove usage of discovery info (#10015) 2021-09-11 11:38:35 -07:00
Michael Irigoyen
360c2cbfa3 Update Material Design Icons to v6.1.95 (#10002)
* Update MDI to v6, add icon deprecation mapping

* Add removed icon path data to build tools

* Resolve incorrect MDI icon import name
2021-09-10 16:49:32 +02:00
Timothy Kist
aba96674f3 Use unicode ellipsis instead of 3 dots "..." (#9997) 2021-09-09 15:43:07 +00:00
Philip Allgaier
5c3d85fc90 Consistent lower-case spelling of "optional" and "required" (#9990) 2021-09-09 15:42:42 +02:00
Philip Allgaier
6486b7fd4c Adjust dev-tools wording: "Active listeners" (#9991) 2021-09-09 15:42:14 +02:00
Paulus Schoutsen
5f3e980de0 Add gas unit error (#9981) 2021-09-07 14:49:08 -07:00
Philip Allgaier
d0edbec5fb Copy resize observer to "non-input" number entity row (#9973) 2021-09-07 17:25:08 +02:00
Ville Skyttä
5d46963e8a Add date device class (#9983) 2021-09-07 17:24:10 +02:00
Philip Allgaier
321f441b63 Handle unavailable vacuums in more-info (#9974) 2021-09-07 10:14:05 +02:00
Paulus Schoutsen
d55bade070 Small tweaks for the create automation from blueprint screen (#9980) 2021-09-07 09:40:25 +02:00
Ville Skyttä
6ba6b821f5 Use SENSOR_DEVICE_CLASS_* constants more (#9982) 2021-09-07 09:21:51 +02:00
Ruben Andrist
b3dedae115 Add ResizeObserver to EntityRow (#9837)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-06 18:34:10 +02:00
Philip Allgaier
5a1070c30f Prevent darkMode overwrite by frontend_default_dark_theme (#9519) 2021-09-06 13:13:01 +02:00
Bram Kragten
40664997e1 Use polyfill from toggleAttribute (#9969) 2021-09-06 11:48:08 +02:00
Philip Allgaier
c6e83cb7c0 Add state_color support to entity card (#9617) 2021-09-06 11:26:16 +02:00
Philip Allgaier
e7e27e794c Add refresh button to history panel (#9958) 2021-09-06 09:19:58 +00:00
Sven Naumann
1073dbe6ab number slider: change column width check from 350px to 300px (#8310) 2021-09-06 11:01:28 +02:00
Philip Allgaier
2bd9b5a015 Prevent index access errors in entity quick bar (#9964)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-09-06 09:01:18 +00:00
Philip Allgaier
bc09febd2c Use integration manifest for service documentation URL (#9960) 2021-09-06 08:58:05 +00:00
Philip Allgaier
b2a87c90a2 Proper editor enum validation of timestamp formats (#9965) 2021-09-06 08:42:42 +00:00
Philip Allgaier
d6dbbcb0de Use dynamic struct validation for entities card rows (#9962) 2021-09-06 10:27:29 +02:00
Philip Allgaier
9ccb5360b3 Add icon to attribute row validation (#9963) 2021-09-06 10:26:11 +02:00
Philip Allgaier
0187c4faff Ensure calender follows time format locale (#9966) 2021-09-06 10:22:46 +02:00
Philip Allgaier
605172a0bc Align internal and fecha date/time formatting (#9380) 2021-09-06 10:22:01 +02:00
Bram Kragten
8565a0d911 Remove float value in float form when emptied (#9947) 2021-09-03 20:07:22 +02:00
Bram Kragten
61c8d23a7e Bunch of minor non breaking dep bumps (#9945) 2021-09-03 10:55:10 -07:00
Joakim Sørensen
5e3487ed59 Don't build wheels for alpine 3.13 (#9944) 2021-09-03 11:56:30 +02:00
Joakim Sørensen
d5a161769c Fix removeBackup function after 2021.9 (#9938) 2021-09-02 22:13:11 +02:00
Bram Kragten
1692f9c2dd Change message to info alert (#9930) 2021-09-02 00:40:51 +02:00
Bram Kragten
0cbac8bb44 Polyfill Array.flat (#9917) 2021-09-01 16:23:41 +02:00
Bram Kragten
35a81e7f11 Correct badge warning (and use new styling) (#9926) 2021-09-01 16:23:29 +02:00
Paulus Schoutsen
ac64d293e7 Sort tags in trigger (#9921) 2021-08-31 21:30:35 -07:00
Bram Kragten
708b8787c5 Bump round slider (#9301) 2021-08-31 08:59:32 +02:00
Bram Kragten
2bddd151eb Don't recreate translation meta in watch mode (#9909) 2021-08-30 13:13:47 -07:00
Bram Kragten
43a585187c Bumped version to 20210830.0 2021-08-30 22:08:49 +02:00
Bram Kragten
324658a36b Fix energy graph when sum is 0 (#9914) 2021-08-30 20:06:58 +00:00
Joakim Sørensen
dd9a9b34d1 Fix width when there is no data in energy cards (#9888)
* Fix width when there is no data in energy cards

* right: 0;
2021-08-30 18:08:48 +02:00
Joakim Sørensen
2ab0e40952 Break overflow in ha-alert (#9885) 2021-08-30 18:08:16 +02:00
Bram Kragten
dfea80ae96 Clarify unit of measurement warning for price entity (#9907)
* Clarify unit of measurement warning for price entity

* Remove unneeded escapes
2021-08-30 15:33:23 +02:00
Bram Kragten
6e38f5accf Align entity errors (#9904) 2021-08-30 08:38:33 +00:00
Bram Kragten
7c952d92bf Fix hat-graph-margin (#9898) 2021-08-28 11:12:02 +00:00
Bram Kragten
2fae0d2d95 Fix tracing graph on iOS (#9897) 2021-08-28 01:04:52 +02:00
Joakim Sørensen
67ab63f00e Use ha-alert for error and warning messages in the supervisor panel (#9892)
* Use ha-alert for error and warning messages in the supervisor panel

* use actionText

* Update hassio/src/dialogs/network/dialog-hassio-network.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-08-27 16:45:05 +02:00
mbo18
719f9c28af Fix missing alarm state in translation: vacation (#9893)
Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2021-08-27 13:24:24 +02:00
Joakim Sørensen
035d621109 Fix delete backup endpoint (#9890) 2021-08-27 12:26:23 +02:00
Joakim Sørensen
791f3b896d Mark "usb" as a discovery source (#9887) 2021-08-26 23:34:23 +02:00
Paulus Schoutsen
fe2172a660 Bumped version to 20210825.0 2021-08-25 10:59:17 -07:00
Pascal Roeleven
640fbd616b Prevent possible parent action-row from switching to yamlMode (#9883)
* Prevent possible parent action-row from switching to yamlMode

Now that we have the choose-action, it's possible to have nested
action-rows. If an action contains a template, we should only switch that
action-row to yamlMode instead of all action-rows.

By canceling the bubbling on the first encouter we prevent the event from
bubbling upwards to parent action-rows.

* Prevent possible parent action-row from also moving

Now that we have the choose-action, it's possible to have nested
action-rows. If an action inside a choose-action is moved, we should only
move that action-row instead of both the action-row and its parents.

By canceling the bubbling on the first encouter we prevent the event from
bubbling upwards to parent action-rows.

* Apply suggestions from code review

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-25 10:55:16 -07:00
Raman Gupta
900efe8a36 Add direct link to zwave_js device's device DB page (#9797)
Co-authored-by: Charles Garwood <cgarwood@newdealmultimedia.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-25 17:53:30 +00:00
Paulus Schoutsen
5bd92d04d9 Get solar forecasts from energy platform (#9794)
* Get solar forecasts from energy platform

* Allow picking other forecasts in settings
2021-08-25 10:40:07 -07:00
Charles Garwood
b15684bcbd Include config entry title in delete confirmation prompt (#9866) 2021-08-25 10:36:39 -07:00
Joakim Sørensen
a93222dbb2 Show history graph for sensors with state_class and not unit (#9879) 2021-08-25 13:41:18 +02:00
Joakim Sørensen
20744e90a0 Use ha-alert in lovelace alerts/warnings (#9880) 2021-08-25 11:24:04 +02:00
Bram Kragten
32777b4259 Add value column prop to ha data table (#9824) 2021-08-25 11:20:35 +02:00
Paulus Schoutsen
271120999c Center nodes in narrow mode (#9878) 2021-08-25 10:22:37 +02:00
Joakim Sørensen
68fe13a67d Use ha-alert in the energy validation results (#9876) 2021-08-24 19:22:40 +02:00
Joakim Sørensen
f3606014c6 Add ha-alert component (#9874) 2021-08-24 09:44:30 -07:00
Joakim Sørensen
efbf4482b2 Build wheels for Alpine 3.14 (#9875) 2021-08-24 11:20:57 +02:00
Jc2k
21a3b4f8e2 Add volatile_organic_compounds device class (#9871) 2021-08-23 11:43:46 +02:00
Paulus Schoutsen
de23b2d046 Do not show domain if equal to title with different casing (#9870) 2021-08-22 23:47:09 -07:00
Paulus Schoutsen
bd8f436c1d Do not render info tooltip if we have an error (#9869) 2021-08-22 23:47:03 -07:00
Bram Kragten
e963735dba Cleanup mjpeg stream when disconnected (+ bump Lit) (#9868) 2021-08-22 22:40:37 -07:00
J. Nick Koston
46c981103d Only update cameras when they are visible (#9690)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-08-22 22:55:56 +00:00
J. Nick Koston
f6d02d8fc6 Cache the camera url without the width and height added (#9778) 2021-08-22 19:17:48 +02:00
J. Nick Koston
e08f691510 Trigger a scan of USB devices when loading integrations (#9860) 2021-08-22 19:14:14 +02:00
Franck Nijhof
af9199aaff Add missing BYN currency (#9863) 2021-08-22 14:40:19 +02:00
Charles Garwood
8576b13f74 Z-Wave JS Heal Node -> Heal Device (#9840) 2021-08-19 10:15:11 +02:00
Paulus Schoutsen
2270d8a795 Bumped version to 20210818.0 2021-08-18 13:16:30 -07:00
Joakim Sørensen
f4dcce6d6c Fix default strategy when energy is not configured (#9827)
* Fix default strategy when energy is not configured

* Address comment

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-18 13:16:12 -07:00
Paulus Schoutsen
b802a410b9 Add energy validation UI (#9802)
* Add basic validation UI

* Also refresh validation results when prefs change

* Update look

* Remove || true

* Add missing errors

* Validate state class

* Rename file

* Simplify energySourcesByType

* Update src/translations/en.json

* Update ha-energy-validation-result.ts
2021-08-18 12:59:41 -07:00
Milan Meulemans
9e3d339ec5 Fix typo in gas paragraph (#9836)
* Fix typo in gas paragraph

* Update src/translations/en.json

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-08-18 04:31:00 +00:00
Bram Kragten
fb97a98b97 Add view_layout to card structs, fix button entity row struct (#9834) 2021-08-17 21:27:15 -07:00
Joakim Sørensen
72773f3bc8 Add disabled by column to entity datatable (#9799) 2021-08-17 21:26:21 -07:00
Franck Nijhof
b0fd93e0c3 Add default icon for gas device class (#9830) 2021-08-17 11:37:53 +02:00
Michael
7aa2ec78f2 Add icons for the device_class 'update' (#9711) 2021-08-17 01:31:29 +02:00
Jc2k
047e856a61 Add new air quality device classes to frontend (#9829) 2021-08-16 23:34:36 +02:00
Bram Kragten
dbe209e3f2 Fix button group in Safari (#9804)
* FIx button group in Safari

* Update src/components/ha-button-toggle-group.ts

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-15 20:45:24 -07:00
Bram Kragten
e0d23ee6cf Make sure idle and random color dont collide (#9808) 2021-08-15 20:45:07 -07:00
Bram Kragten
7a35f46370 Set suggested min max to min and max (#9805) 2021-08-15 20:44:42 -07:00
Bram Kragten
4a4465efb6 FIx device graph not rendering right when changing period (#9806) 2021-08-15 20:44:12 -07:00
Bram Kragten
3a112531cc Make energy period selector less wide for mobile (#9814) 2021-08-15 20:43:35 -07:00
Bram Kragten
456209dded Don't start video after the element was removed from DOM (#9813) 2021-08-15 20:43:00 -07:00
Bram Kragten
2556b0d157 Improve iOS 12 check (#9816) 2021-08-15 20:42:39 -07:00
Bram Kragten
1e8903fd76 There are 5 steps now (#9822) 2021-08-15 20:42:20 -07:00
Bram Kragten
ad9f18c231 Keep datasets hidden after data update (#9823) 2021-08-15 20:36:15 -07:00
Bram Kragten
63e3de00cb Remove battery from demo (#9801)
* Remove gas and battery from demo

* Update energy.ts

* Just battery
2021-08-13 12:09:49 -07:00
Bram Kragten
d06ffeeede Bumped version to 20210813.0 2021-08-13 19:42:25 +02:00
Bram Kragten
3479fb9d94 Add gas to energy dashboard (#9787) 2021-08-13 10:39:20 -07:00
Paulus Schoutsen
304bd002ae Netto -> Net (#9798) 2021-08-13 08:48:23 +02:00
Bram Kragten
5dad18c85f Make time inputs the same through the UI (#9766) 2021-08-12 22:52:26 +02:00
Bram Kragten
19e4c0657a Add battery to energy dashboard (#9757) 2021-08-12 08:40:21 -07:00
Bram Kragten
44548fdc33 Fix tracing graph (#9782) 2021-08-12 15:59:57 +02:00
Bram Kragten
d8929074b5 Use correct vars for gauge colors (#9727) 2021-08-12 15:59:42 +02:00
Bram Kragten
e11532ae92 Update translations (#9784) 2021-08-12 12:38:52 +02:00
Joakim Sørensen
eff48acdf4 Rename snapshot -> backup (#9393)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-08-12 11:56:13 +02:00
Onur-d
a9850f9641 Add max-width for Logos to 100% (#9779) 2021-08-11 17:19:12 +02:00
Joakim Sørensen
aab0b8a3ce Request darktheme optimized brand images if dark theme is used (#9777) 2021-08-11 15:15:12 +02:00
Philip Allgaier
b12e062d94 Fix "wan't" typos in energy panel (#9775) 2021-08-11 09:32:32 +02:00
J. Nick Koston
b36e342f15 Pass the width and height when requesting camera images (#9683)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-10 21:42:37 -07:00
Bram Kragten
f686816c86 Cleanup of tracing graph (#9564) 2021-08-11 01:23:07 +02:00
Bram Kragten
dc50e54afc Add period selection to energy dashboard (#9756) 2021-08-11 01:22:27 +02:00
Paulus Schoutsen
3897e3d452 Fix language string (#9767) 2021-08-10 21:22:38 +02:00
Bram Kragten
2557b03b11 Catch failing to fetch forecasts (#9765) 2021-08-10 19:47:09 +02:00
Paulus Schoutsen
29d29a337f Do not query energy prefs if it's not loaded (#9763) 2021-08-10 10:40:51 -07:00
Bram Kragten
34f8e5e28d Bumped version to 20210809.0 2021-08-09 20:44:31 +02:00
Bram Kragten
afd1a68c62 Change some wording (#9742) 2021-08-09 20:43:54 +02:00
Bram Kragten
dbcf1cb907 Group solar forecasts by hour (#9728) 2021-08-09 17:56:25 +02:00
Paulus Schoutsen
9ca64f9789 Show reconnecting when not connected and auto-connect is on (#9738) 2021-08-07 11:46:26 +02:00
Bram Kragten
4c247ac49d Merge branch 'master' into dev 2021-08-04 20:18:00 +02:00
Bram Kragten
e8a406526b Bumped version to 20210804.0 2021-08-04 20:17:05 +02:00
Bram Kragten
7fcea16c6b Increase font size tooltips + position then static (#9713) 2021-08-04 09:19:38 -07:00
Bram Kragten
028b799d2c Default energy collection key should not be defined (#9710) 2021-08-04 08:25:42 -07:00
Bram Kragten
3485296e23 Fix typo (#9707) 2021-08-04 12:36:15 +02:00
Paulus Schoutsen
03078cdd45 TOML 2021-08-03 22:05:14 -07:00
Paulus Schoutsen
740310800d SET NODE_OPTIONS 2021-08-03 22:05:14 -07:00
Paulus Schoutsen
6d7c558482 TOML 2021-08-03 22:02:38 -07:00
Paulus Schoutsen
fdb10515c3 SET NODE_OPTIONS 2021-08-03 22:00:13 -07:00
Paulus Schoutsen
5a0f13c485 Specify YARN version on netlify (#9705) 2021-08-03 21:56:02 -07:00
Paulus Schoutsen
80b330ad7b Specify YARN version on netlify (#9705) 2021-08-03 21:55:15 -07:00
Bram Kragten
d929e1c134 Add support for statistics card to demo (#9703) 2021-08-03 15:16:21 -07:00
Bram Kragten
5e40dcdc38 Add support for statistics card to demo (#9703) 2021-08-03 15:16:08 -07:00
Paulus Schoutsen
1dd3e2a83b 20210803.2 (#9704)
* Add energy distribution card to arsaboo demo (#9702)

* Add energy distribution card to arsaboo demo

* Make link dashboard conform

* Bumped version to 20210803.2
2021-08-03 15:15:21 -07:00
Paulus Schoutsen
a62742fad9 Bumped version to 20210803.2 2021-08-03 15:14:46 -07:00
Paulus Schoutsen
1f9c45b11c Add energy distribution card to arsaboo demo (#9702)
* Add energy distribution card to arsaboo demo

* Make link dashboard conform
2021-08-03 22:09:08 +00:00
Bram Kragten
68bec5e158 Merge pull request #9701 from home-assistant/dev
20210803.1
2021-08-03 23:36:27 +02:00
Bram Kragten
37f1bd7d63 Bumped version to 20210803.1 2021-08-03 23:22:46 +02:00
Bram Kragten
5ba24211e2 Add messages when no data (#9700)
* Add messages when no data

* 2 hours

* comment
2021-08-03 23:22:32 +02:00
Bram Kragten
d699647418 Fix calculate sum growth + refreshing too often (#9699) 2021-08-03 23:19:26 +02:00
Bram Kragten
b246502cb6 Energy demo (#9698)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-08-03 09:50:31 -07:00
Bram Kragten
9b33ead8aa Final energy tweaks (#9694) 2021-08-03 08:54:10 -07:00
Milan Meulemans
32e8c1dc6d Remove energy currency translation (#9695) 2021-08-03 11:00:21 +00:00
Paulus Schoutsen
e09ef7862e Merge pull request #9692 from home-assistant/dev 2021-08-02 21:10:24 -07:00
Paulus Schoutsen
85420304d0 Bumped version to 20210803.0 2021-08-02 20:51:41 -07:00
Bram Kragten
1c097a669d Add currency to onboarding (#9691) 2021-08-02 21:02:57 +00:00
Bram Kragten
4e1497c5da Change layout of period selector (#9688) 2021-08-02 13:57:59 -07:00
Bram Kragten
49d426675f Masonry (#9689) 2021-08-02 13:57:39 -07:00
Paulus Schoutsen
dc6ac668b4 Merge pull request #9687 from home-assistant/add-description-devices-dialog
Add description to statistic picker in add device dialog
2021-08-02 10:30:24 -07:00
Paulus Schoutsen
4ee24b0845 Add description to statistic picker in add device dialog 2021-08-02 10:18:59 -07:00
Paulus Schoutsen
ba20aef206 Merge pull request #9685 from home-assistant/dev 2021-08-02 10:07:14 -07:00
Paulus Schoutsen
41ef6133c1 Bumped version to 20210802.0 2021-08-02 09:51:10 -07:00
Joakim Sørensen
50bd5ee8f7 Add my support for energy (#9681)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-08-02 09:50:47 -07:00
Joakim Sørensen
285f3fe330 Fix text color for hass-tabs-subpage with narrow (#9680) 2021-08-02 09:50:24 -07:00
Bram Kragten
4d01199986 Clear all energy collection prefs (#9684) 2021-08-02 09:39:01 -07:00
Paulus Schoutsen
bcc0052fe0 Update text 2021-08-02 08:45:05 -07:00
Paulus Schoutsen
4b592d81bd Simplify date picker and put in sidebar (#9677)
* Simplify date picker and put in sidebar

* Convert picker to reusable component

* Put date picker in top bar on desktop

* Chefs
2021-08-02 08:18:49 -07:00
Bram Kragten
884e323288 Make clear it is showing today (#9679) 2021-08-02 07:22:36 -07:00
Bram Kragten
78b799dd05 Add cost stat ids (#9678) 2021-08-02 07:22:17 -07:00
Paulus Schoutsen
847fa2e700 Add energy distribution card to auto lovelace if energy configured (#9675)
* Add energy distribution card to auto lovelace if energy configured

* Add import

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-08-02 12:36:13 +00:00
Paulus Schoutsen
481a79e311 Allow specifying collection keys so energy dashboard has own (#9674) 2021-08-02 14:23:15 +02:00
Paulus Schoutsen
f19f2ff321 Add tooltips to the gauges (#9676) 2021-08-02 14:21:41 +02:00
Bram Kragten
6dc4d7bb70 Merge pull request #9673 from home-assistant/dev
20210801.0
2021-08-01 23:40:48 +02:00
Bram Kragten
83460a34f4 Bumped version to 20210801.0 2021-08-01 23:25:24 +02:00
Bram Kragten
2adbb47373 Refresh stats at 20 minutes past the hour every hour (#9667) 2021-08-01 21:24:41 +00:00
Bram Kragten
2159a5419a Fix device energy graph height (#9666)
* Fix device energy graph height

* Update state-history-chart-timeline.ts

* Update hui-energy-devices-graph-card.ts
2021-08-01 13:59:34 -07:00
Bram Kragten
044d6a15d9 Add view type selector to view editor (#9671) 2021-08-01 22:55:51 +02:00
Bram Kragten
b6055062c6 Don's start at zero so growth is beter visible + calc sum state correctly (#9672) 2021-08-01 22:55:41 +02:00
Bram Kragten
6fd85e043b Sidebar view: Allow to move card position from UI (#9669) 2021-08-01 18:17:34 +02:00
Bram Kragten
e07ac52356 Add UOM to stats chart, fix coloring of bands (#9665) 2021-07-31 20:36:27 +02:00
Paulus Schoutsen
0f16ba9325 Speed up data loading and allow embedding individual energy cards (#9660)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-31 18:33:41 +02:00
Bram Kragten
378e6d28bc Merge pull request #9659 from home-assistant/dev 2021-07-30 22:29:51 +02:00
Bram Kragten
539d2b880c Bumped version to 20210730.0 2021-07-30 22:14:21 +02:00
Bram Kragten
2982adbfa7 Fix return compensation not being negative (#9654) 2021-07-30 12:57:27 -07:00
Bram Kragten
5147dff670 Sidebar view: Move all cards to 1 column on mobile (#9656) 2021-07-30 12:57:01 -07:00
Bram Kragten
2cdf78c504 Add grid neutrality gauge (#9655) 2021-07-30 12:55:58 -07:00
Bram Kragten
cfad45b7c2 fix and finish statistics card (#9658) 2021-07-30 12:52:38 -07:00
Bram Kragten
5234e9bce5 still round numbers when not formatting them (#9653) 2021-07-30 17:36:31 +02:00
Bram Kragten
0ed5454d02 fix self consumed solar (#9651) 2021-07-30 12:28:47 +02:00
Thomas Lovén
03080973be Add needle option to ha-gauge and gauge card (#9637) 2021-07-30 10:51:21 +02:00
Paulus Schoutsen
a4f51b0cb3 Fix label for device consumption (#9648) 2021-07-30 09:42:54 +02:00
Bram Kragten
a7a8aaa887 Merge pull request #9608 from home-assistant/dev
20210726.0
2021-07-26 23:04:05 +02:00
Bram Kragten
5c737e1969 Merge pull request #9517 from home-assistant/dev 2021-07-07 09:58:19 +02:00
Bram Kragten
fabbcac99f Merge pull request #9510 from home-assistant/dev 2021-07-06 11:09:42 +02:00
Bram Kragten
7bab245073 Merge pull request #9483 from home-assistant/dev 2021-06-30 12:17:54 +02:00
Bram Kragten
8666e6baae Merge pull request #9358 from home-assistant/dev 2021-06-03 23:12:30 +02:00
Bram Kragten
2db9f33c41 Merge pull request #9334 from home-assistant/dev 2021-06-01 21:53:08 +02:00
Bram Kragten
3d788d6056 Merge pull request #9324 from home-assistant/dev 2021-06-01 11:56:02 +02:00
Paulus Schoutsen
7560f98d70 Merge pull request #9320 from home-assistant/dev 2021-05-31 15:55:09 -07:00
Paulus Schoutsen
1533c22d5c Merge pull request #9310 from home-assistant/dev 2021-05-30 20:30:44 -07:00
Bram Kragten
cf03f103ab Merge pull request #9285 from home-assistant/dev 2021-05-28 12:45:21 +02:00
Bram Kragten
4a8a7c997e Merge pull request #9267 from home-assistant/dev 2021-05-26 17:33:32 +02:00
Bram Kragten
9612bc78fe Merge pull request #9097 from home-assistant/dev 2021-05-04 23:21:05 +02:00
Bram Kragten
2b86137388 Merge pull request #9079 from home-assistant/dev 2021-05-03 16:16:58 +02:00
Paulus Schoutsen
8fdbe447c1 Merge pull request #9060 from home-assistant/dev
20210430.0
2021-04-30 12:43:33 -07:00
Bram Kragten
764ae7e0b6 Merge pull request #9045 from home-assistant/dev 2021-04-29 22:21:03 +02:00
Paulus Schoutsen
6b7e78320d Merge pull request #9024 from home-assistant/dev 2021-04-28 10:47:16 -07:00
622 changed files with 29345 additions and 19747 deletions

View File

@@ -1,9 +1,10 @@
{ {
"extends": [ "extends": [
"airbnb-base",
"airbnb-typescript/base", "airbnb-typescript/base",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:wc/recommended", "plugin:wc/recommended",
"plugin:lit/recommended", "plugin:lit/all",
"prettier" "prettier"
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
@@ -109,7 +110,9 @@
} }
], ],
"unused-imports/no-unused-imports": "error", "unused-imports/no-unused-imports": "error",
"lit/attribute-value-entities": "off" "lit/attribute-value-entities": "off",
"lit/no-template-map": "off",
"lit/no-template-arrow": "warn"
}, },
"plugins": ["disable", "unused-imports"], "plugins": ["disable", "unused-imports"],
"processor": "disable/disable" "processor": "disable/disable"

View File

@@ -12,7 +12,7 @@ on:
env: env:
NODE_VERSION: 14 NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096 NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
lint: lint:
@@ -53,8 +53,8 @@ jobs:
run: yarn install run: yarn install
env: env:
CI: true CI: true
- name: Run Mocha - name: Run Tests
run: yarn run mocha run: yarn run test
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, test] needs: [lint, test]

View File

@@ -7,7 +7,7 @@ on:
env: env:
NODE_VERSION: 14 NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096 NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
deploy: deploy:

View File

@@ -8,7 +8,7 @@ on:
env: env:
PYTHON_VERSION: 3.8 PYTHON_VERSION: 3.8
NODE_VERSION: 14 NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096 NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
release: release:
@@ -73,8 +73,7 @@ jobs:
matrix: matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag: tag:
- "3.8-alpine3.12" - "3.9-alpine3.14"
- "3.9-alpine3.13"
steps: steps:
- name: Download requirements.txt - name: Download requirements.txt
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2

View File

@@ -1,4 +0,0 @@
module.exports = {
require: "test-mocha/testconf.js",
timeout: 10000,
};

File diff suppressed because one or more lines are too long

631
.yarn/releases/yarn-3.0.2.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -6,4 +6,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools" spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-2.4.2.cjs yarnPath: .yarn/releases/yarn-3.0.2.cjs

View File

@@ -82,6 +82,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
// Only support the syntax, Webpack will handle it. // Only support the syntax, Webpack will handle it.
"@babel/plugin-syntax-import-meta", "@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import", "@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-top-level-await",
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],

View File

@@ -5,32 +5,32 @@ require("./translations");
gulp.task( gulp.task(
"clean", "clean",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { gulp.parallel("clean-translations", () =>
return del([paths.app_output_root, paths.build_dir]); del([paths.app_output_root, paths.build_dir])
}) )
); );
gulp.task( gulp.task(
"clean-demo", "clean-demo",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { gulp.parallel("clean-translations", () =>
return del([paths.demo_output_root, paths.build_dir]); del([paths.demo_output_root, paths.build_dir])
}) )
); );
gulp.task( gulp.task(
"clean-cast", "clean-cast",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { gulp.parallel("clean-translations", () =>
return del([paths.cast_output_root, paths.build_dir]); del([paths.cast_output_root, paths.build_dir])
}) )
); );
gulp.task("clean-hassio", function cleanOutputAndBuildDir() { gulp.task("clean-hassio", () =>
return del([paths.hassio_output_root, paths.build_dir]); del([paths.hassio_output_root, paths.build_dir])
}); );
gulp.task( gulp.task(
"clean-gallery", "clean-gallery",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { gulp.parallel("clean-translations", () =>
return del([paths.gallery_output_root, paths.build_dir]); del([paths.gallery_output_root, paths.build_dir])
}) )
); );

View File

@@ -12,8 +12,10 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) => const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile))); fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath = (staticDir) => (...parts) => const genStaticPath =
path.resolve(staticDir, ...parts); (staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) { function copyTranslations(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const crypto = require("crypto"); const crypto = require("crypto");
const del = require("del"); const del = require("del");
const path = require("path"); const path = require("path");
@@ -26,13 +28,6 @@ gulp.task("translations-enable-merge-backend", (done) => {
done(); done();
}); });
String.prototype.rsplit = function (sep, maxsplit) {
var split = this.split(sep);
return maxsplit
? [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit))
: split;
};
// Panel translations which should be split from the core translations. // Panel translations which should be split from the core translations.
const TRANSLATION_FRAGMENTS = Object.keys( const TRANSLATION_FRAGMENTS = Object.keys(
require("../../src/translations/en.json").ui.panel require("../../src/translations/en.json").ui.panel
@@ -40,7 +35,7 @@ const TRANSLATION_FRAGMENTS = Object.keys(
function recursiveFlatten(prefix, data) { function recursiveFlatten(prefix, data) {
let output = {}; let output = {};
Object.keys(data).forEach(function (key) { Object.keys(data).forEach((key) => {
if (typeof data[key] === "object") { if (typeof data[key] === "object") {
output = { output = {
...output, ...output,
@@ -101,15 +96,19 @@ function lokaliseTransform(data, original, file) {
if (value instanceof Object) { if (value instanceof Object) {
output[key] = lokaliseTransform(value, original, file); output[key] = lokaliseTransform(value, original, file);
} else { } else {
output[key] = value.replace(re_key_reference, (match, key) => { output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
const replace = key.split("::").reduce((tr, k) => { const replace = lokalise_key.split("::").reduce((tr, k) => {
if (!tr) { if (!tr) {
throw Error(`Invalid key placeholder ${key} in ${file.path}`); throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
} }
return tr[k]; return tr[k];
}, original); }, original);
if (typeof replace !== "string") { if (typeof replace !== "string") {
throw Error(`Invalid key placeholder ${key} in ${file.path}`); throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
} }
return replace; return replace;
}); });
@@ -118,9 +117,7 @@ function lokaliseTransform(data, original, file) {
return output; return output;
} }
gulp.task("clean-translations", function () { gulp.task("clean-translations", () => del([workDir]));
return del([workDir]);
});
gulp.task("ensure-translations-build-dir", (done) => { gulp.task("ensure-translations-build-dir", (done) => {
if (!fs.existsSync(workDir)) { if (!fs.existsSync(workDir)) {
@@ -129,7 +126,7 @@ gulp.task("ensure-translations-build-dir", (done) => {
done(); done();
}); });
gulp.task("create-test-metadata", function (cb) { gulp.task("create-test-metadata", (cb) => {
fs.writeFile( fs.writeFile(
workDir + "/testMetadata.json", workDir + "/testMetadata.json",
JSON.stringify({ JSON.stringify({
@@ -143,17 +140,13 @@ gulp.task("create-test-metadata", function (cb) {
gulp.task( gulp.task(
"create-test-translation", "create-test-translation",
gulp.series("create-test-metadata", function createTestTranslation() { gulp.series("create-test-metadata", () =>
return gulp gulp
.src(path.join(paths.translations_src, "en.json")) .src(path.join(paths.translations_src, "en.json"))
.pipe( .pipe(transform((data, _file) => recursiveEmpty(data)))
transform(function (data, file) {
return recursiveEmpty(data);
})
)
.pipe(rename("test.json")) .pipe(rename("test.json"))
.pipe(gulp.dest(workDir)); .pipe(gulp.dest(workDir))
}) )
); );
/** /**
@@ -165,7 +158,7 @@ gulp.task(
* project is buildable immediately after merging new translation keys, since * project is buildable immediately after merging new translation keys, since
* the Lokalise update to translations/en.json will not happen immediately. * the Lokalise update to translations/en.json will not happen immediately.
*/ */
gulp.task("build-master-translation", function () { gulp.task("build-master-translation", () => {
const src = [path.join(paths.translations_src, "en.json")]; const src = [path.join(paths.translations_src, "en.json")];
if (mergeBackend) { if (mergeBackend) {
@@ -174,11 +167,7 @@ gulp.task("build-master-translation", function () {
return gulp return gulp
.src(src) .src(src)
.pipe( .pipe(transform((data, file) => lokaliseTransform(data, data, file)))
transform(function (data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe( .pipe(
merge({ merge({
fileName: "translationMaster.json", fileName: "translationMaster.json",
@@ -187,18 +176,14 @@ gulp.task("build-master-translation", function () {
.pipe(gulp.dest(workDir)); .pipe(gulp.dest(workDir));
}); });
gulp.task("build-merged-translations", function () { gulp.task("build-merged-translations", () =>
return gulp gulp
.src([inFrontendDir + "/*.json", workDir + "/test.json"], { .src([inFrontendDir + "/*.json", workDir + "/test.json"], {
allowEmpty: true, allowEmpty: true,
}) })
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe( .pipe(
transform(function (data, file) { foreach((stream, file) => {
return lokaliseTransform(data, data, file);
})
)
.pipe(
foreach(function (stream, file) {
// For each language generate a merged json file. It begins with the master // For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent // translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag // tags into one file for each specific subtag
@@ -230,17 +215,17 @@ gulp.task("build-merged-translations", function () {
) )
.pipe(gulp.dest(fullDir)); .pipe(gulp.dest(fullDir));
}) })
); )
}); );
var taskName; let taskName;
const splitTasks = []; const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => { TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = "build-translation-fragment-" + fragment; taskName = "build-translation-fragment-" + fragment;
gulp.task(taskName, function () { gulp.task(taskName, () =>
// Return only the translations for this fragment. // Return only the translations for this fragment.
return gulp gulp
.src(fullDir + "/*.json") .src(fullDir + "/*.json")
.pipe( .pipe(
transform((data) => ({ transform((data) => ({
@@ -251,18 +236,18 @@ TRANSLATION_FRAGMENTS.forEach((fragment) => {
}, },
})) }))
) )
.pipe(gulp.dest(workDir + "/" + fragment)); .pipe(gulp.dest(workDir + "/" + fragment))
}); );
splitTasks.push(taskName); splitTasks.push(taskName);
}); });
taskName = "build-translation-core"; taskName = "build-translation-core";
gulp.task(taskName, function () { gulp.task(taskName, () =>
// Remove the fragment translations from the core translation. // Remove the fragment translations from the core translation.
return gulp gulp
.src(fullDir + "/*.json") .src(fullDir + "/*.json")
.pipe( .pipe(
transform((data, file) => { transform((data, _file) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => { TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment]; delete data.ui.panel[fragment];
}); });
@@ -270,14 +255,14 @@ gulp.task(taskName, function () {
return data; return data;
}) })
) )
.pipe(gulp.dest(coreDir)); .pipe(gulp.dest(coreDir))
}); );
splitTasks.push(taskName); splitTasks.push(taskName);
gulp.task("build-flattened-translations", function () { gulp.task("build-flattened-translations", () =>
// Flatten the split versions of our translations, and move them into outDir // Flatten the split versions of our translations, and move them into outDir
return gulp gulp
.src( .src(
TRANSLATION_FRAGMENTS.map( TRANSLATION_FRAGMENTS.map(
(fragment) => workDir + "/" + fragment + "/*.json" (fragment) => workDir + "/" + fragment + "/*.json"
@@ -285,41 +270,45 @@ gulp.task("build-flattened-translations", function () {
{ base: workDir } { base: workDir }
) )
.pipe( .pipe(
transform(function (data) { transform((data) =>
// Polymer.AppLocalizeBehavior requires flattened json // Polymer.AppLocalizeBehavior requires flattened json
return flatten(data); flatten(data)
}) )
) )
.pipe( .pipe(
rename((filePath) => { rename((filePath) => {
if (filePath.dirname === "core") { if (filePath.dirname === "core") {
filePath.dirname = ""; filePath.dirname = "";
} }
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
}) })
) )
.pipe(gulp.dest(outDir)); .pipe(gulp.dest(outDir))
}); );
const fingerprints = {}; const fingerprints = {};
gulp.task( gulp.task("build-translation-fingerprints", () => {
"build-translation-fingerprints", // Fingerprint full file of each language
function fingerprintTranslationFiles() { const files = fs.readdirSync(fullDir);
// Fingerprint full file of each language
const files = fs.readdirSync(fullDir);
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = { fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes // In dev we create fake hashes
hash: env.isProdBuild() hash: env.isProdBuild()
? crypto ? crypto
.createHash("md5") .createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8")) .update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex") .digest("hex")
: "dev", : "dev",
}; };
} }
// In dev we create the file with the fake hash in the filename
if (env.isProdBuild()) {
mapFiles(outDir, ".json", (filename) => { mapFiles(outDir, ".json", (filename) => {
const parsed = path.parse(filename); const parsed = path.parse(filename);
@@ -335,35 +324,43 @@ gulp.task(
}` }`
); );
}); });
const stream = source("translationFingerprints.json");
stream.write(JSON.stringify(fingerprints));
process.nextTick(() => stream.end());
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
} }
);
gulp.task("build-translation-fragment-supervisor", function () { const stream = source("translationFingerprints.json");
return gulp stream.write(JSON.stringify(fingerprints));
process.nextTick(() => stream.end());
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
});
gulp.task("build-translation-fragment-supervisor", () =>
gulp
.src(fullDir + "/*.json") .src(fullDir + "/*.json")
.pipe(transform((data) => data.supervisor)) .pipe(transform((data) => data.supervisor))
.pipe(gulp.dest(workDir + "/supervisor"));
});
gulp.task("build-translation-flatten-supervisor", function () {
return gulp
.src(workDir + "/supervisor/*.json")
.pipe( .pipe(
transform(function (data) { rename((filePath) => {
// Polymer.AppLocalizeBehavior requires flattened json // In dev we create the file with the fake hash in the filename
return flatten(data); if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
}) })
) )
.pipe(gulp.dest(outDir)); .pipe(gulp.dest(workDir + "/supervisor"))
}); );
gulp.task("build-translation-write-metadata", function writeMetadata() { gulp.task("build-translation-flatten-supervisor", () =>
return gulp gulp
.src(workDir + "/supervisor/*.json")
.pipe(
transform((data) =>
// Polymer.AppLocalizeBehavior requires flattened json
flatten(data)
)
)
.pipe(gulp.dest(outDir))
);
gulp.task("build-translation-write-metadata", () =>
gulp
.src( .src(
[ [
path.join(paths.translations_src, "translationMetadata.json"), path.join(paths.translations_src, "translationMetadata.json"),
@@ -374,13 +371,14 @@ gulp.task("build-translation-write-metadata", function writeMetadata() {
) )
.pipe(merge({})) .pipe(merge({}))
.pipe( .pipe(
transform(function (data) { transform((data) => {
const newData = {}; const newData = {};
Object.entries(data).forEach(([key, value]) => { Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name. // Filter out translations without native name.
if (value.nativeName) { if (value.nativeName) {
newData[key] = value; newData[key] = value;
} else { } else {
// eslint-disable-next-line no-console
console.warn( console.warn(
`Skipping language ${key}. Native name was not translated.` `Skipping language ${key}. Native name was not translated.`
); );
@@ -396,19 +394,26 @@ gulp.task("build-translation-write-metadata", function writeMetadata() {
})) }))
) )
.pipe(rename("translationMetadata.json")) .pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir)); .pipe(gulp.dest(workDir))
}); );
gulp.task(
"create-translations",
gulp.series(
env.isProdBuild() ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations"
)
);
gulp.task( gulp.task(
"build-translations", "build-translations",
gulp.series( gulp.series(
"clean-translations", "clean-translations",
"ensure-translations-build-dir", "ensure-translations-build-dir",
env.isProdBuild() ? (done) => done() : "create-test-translation", "create-translations",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations",
"build-translation-fingerprints", "build-translation-fingerprints",
"build-translation-write-metadata" "build-translation-write-metadata"
) )

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Tasks to run webpack. // Tasks to run webpack.
const fs = require("fs"); const fs = require("fs");
const gulp = require("gulp"); const gulp = require("gulp");
@@ -44,7 +45,7 @@ const runDevServer = ({
open: true, open: true,
watchContentBase: true, watchContentBase: true,
contentBase, contentBase,
}).listen(port, listenHost, function (err) { }).listen(port, listenHost, (err) => {
if (err) { if (err) {
throw err; throw err;
} }
@@ -65,6 +66,7 @@ const doneHandler = (done) => (err, stats) => {
} }
if (stats.hasErrors() || stats.hasWarnings()) { if (stats.hasErrors() || stats.hasWarnings()) {
// eslint-disable-next-line no-console
console.log(stats.toString("minimal")); console.log(stats.toString("minimal"));
} }
@@ -90,16 +92,10 @@ gulp.task("webpack-watch-app", () => {
process.env.ES5 process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false }) ? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true }) : createAppConfig({ isProdBuild: false, latestBuild: true })
).watch( ).watch({ poll: isWsl }, doneHandler());
{
ignored: /build-translations/,
poll: isWsl,
},
doneHandler()
);
gulp.watch( gulp.watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
gulp.series("build-translations", "copy-translations-app") gulp.series("create-translations", "copy-translations-app")
); );
}); });

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@ const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const paths = require("./paths.js"); const paths = require("./paths.js");
const bundle = require("./bundle.js"); const bundle = require("./bundle.js");
const log = require("fancy-log"); const log = require("fancy-log");
const WebpackBar = require("webpackbar");
class LogStartCompilePlugin { class LogStartCompilePlugin {
ignoredFirst = false; ignoredFirst = false;
@@ -74,6 +75,7 @@ const createWebpackConfig = ({
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
}, },
plugins: [ plugins: [
new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({ new WebpackManifestPlugin({
// Only include the JS of entrypoints // Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"), filter: (file) => file.isInitial && !file.name.endsWith(".map"),
@@ -125,6 +127,13 @@ const createWebpackConfig = ({
alias: { alias: {
"lit/decorators$": "lit/decorators.js", "lit/decorators$": "lit/decorators.js",
"lit/directive$": "lit/directive.js", "lit/directive$": "lit/directive.js",
"lit/directives/until$": "lit/directives/until.js",
"lit/directives/class-map$": "lit/directives/class-map.js",
"lit/directives/style-map$": "lit/directives/style-map.js",
"lit/directives/if-defined$": "lit/directives/if-defined.js",
"lit/directives/guard$": "lit/directives/guard.js",
"lit/directives/cache$": "lit/directives/cache.js",
"lit/directives/repeat$": "lit/directives/repeat.js",
"lit/polyfill-support$": "lit/polyfill-support.js", "lit/polyfill-support$": "lit/polyfill-support.js",
}, },
}, },
@@ -142,6 +151,9 @@ const createWebpackConfig = ({
// To silence warning in worker plugin // To silence warning in worker plugin
globalObject: "self", globalObject: "self",
}, },
experiments: {
topLevelAwait: true,
},
}; };
}; };

View File

@@ -191,7 +191,7 @@ class HcCast extends LitElement {
} }
this.connection.close(); this.connection.close();
location.reload(); location.reload();
} catch (err) { } catch (err: any) {
alert("Unable to log out!"); alert("Unable to log out!");
} }
} }

View File

@@ -212,7 +212,7 @@ export class HcConnect extends LitElement {
let url: URL; let url: URL;
try { try {
url = new URL(value); url = new URL(value);
} catch (err) { } catch (err: any) {
this.error = "Invalid URL"; this.error = "Invalid URL";
return; return;
} }
@@ -240,7 +240,7 @@ export class HcConnect extends LitElement {
try { try {
this.loading = true; this.loading = true;
auth = await getAuth(options); auth = await getAuth(options);
} catch (err) { } catch (err: any) {
if (init === "saved-tokens" && err === ERR_CANNOT_CONNECT) { if (init === "saved-tokens" && err === ERR_CANNOT_CONNECT) {
this.cannotConnect = true; this.cannotConnect = true;
return; return;
@@ -259,7 +259,7 @@ export class HcConnect extends LitElement {
try { try {
conn = await createConnection({ auth }); conn = await createConnection({ auth });
} catch (err) { } catch (err: any) {
// In case of saved tokens, silently solve problems. // In case of saved tokens, silently solve problems.
if (init === "saved-tokens") { if (init === "saved-tokens") {
if (err === ERR_CANNOT_CONNECT) { if (err === ERR_CANNOT_CONNECT) {
@@ -285,7 +285,7 @@ export class HcConnect extends LitElement {
try { try {
saveTokens(null); saveTokens(null);
location.reload(); location.reload();
} catch (err) { } catch (err: any) {
alert("Unable to log out!"); alert("Unable to log out!");
} }
} }

View File

@@ -148,14 +148,14 @@ export class HcMain extends HassElement {
expires_in: 0, expires_in: 0,
}), }),
}); });
} catch (err) { } catch (err: any) {
this._error = this._getErrorMessage(err); this._error = this._getErrorMessage(err);
return; return;
} }
let connection; let connection;
try { try {
connection = await createConnection({ auth }); connection = await createConnection({ auth });
} catch (err) { } catch (err: any) {
this._error = this._getErrorMessage(err); this._error = this._getErrorMessage(err);
return; return;
} }
@@ -193,7 +193,7 @@ export class HcMain extends HassElement {
this._unsubLovelace = llColl.subscribe((lovelaceConfig) => this._unsubLovelace = llColl.subscribe((lovelaceConfig) =>
this._handleNewLovelaceConfig(lovelaceConfig) this._handleNewLovelaceConfig(lovelaceConfig)
); );
} catch (err) { } catch (err: any) {
// eslint-disable-next-line // eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg); console.log("Error fetching Lovelace configuration", err, msg);
// Generate a Lovelace config. // Generate a Lovelace config.

View File

@@ -29,6 +29,11 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
}, },
], ],
}, },
{
title: "Energy distribution today",
type: "energy-distribution",
link_dashboard: true,
},
{ {
type: "thermostat", type: "thermostat",
entity: "climate.upstairs", entity: "climate.upstairs",

View File

@@ -1,5 +1,6 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace } from "../../../src/panels/lovelace/types"; import { Lovelace } from "../../../src/panels/lovelace/types";
import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types"; import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
@@ -27,6 +28,7 @@ export const setDemoConfig = async (
selectedDemoConfig = confProm; selectedDemoConfig = confProm;
hass.addEntities(config.entities(hass.localize), true); hass.addEntities(config.entities(hass.localize), true);
hass.addEntities(energyEntities());
lovelace.saveConfig(config.lovelace(hass.localize)); lovelace.saveConfig(config.lovelace(hass.localize));
hass.mockTheme(config.theme()); hass.mockTheme(config.theme());
}; };

View File

@@ -44,7 +44,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
(conf) => html` (conf) => html`
${conf.name} ${conf.name}
<small> <small>
<a target="_blank" href="${conf.authorUrl}"> <a target="_blank" href=${conf.authorUrl}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.page-demo.cards.demo.demo_by", "ui.panel.page-demo.cards.demo.demo_by",
"name", "name",
@@ -94,7 +94,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
this._switching = true; this._switching = true;
try { try {
await setDemoConfig(this.hass, this.lovelace!, index); await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err) { } catch (err: any) {
alert("Failed to switch config :-("); alert("Failed to switch config :-(");
} finally { } finally {
this._switching = false; this._switching = false;

View File

@@ -20,6 +20,9 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log"; import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template"; import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations"; import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl { class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() { protected async _initializeHass() {
@@ -47,8 +50,12 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass); mockEvents(hass);
mockMediaPlayer(hass); mockMediaPlayer(hass);
mockFrontend(hass); mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass); mockPersistentNotification(hass);
hass.addEntities(energyEntities());
// Once config is loaded AND localize, set entities and apply theme. // Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then( Promise.all([selectedDemoConfig, localizePromise]).then(
([conf, localize]) => { ([conf, localize]) => {

41
demo/src/stubs/config.ts Normal file
View File

@@ -0,0 +1,41 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
hass.mockWS("config/entity_registry/list", () => [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
},
]);
};

134
demo/src/stubs/energy.ts Normal file
View File

@@ -0,0 +1,134 @@
import { format, startOfToday, startOfTomorrow } from "date-fns";
import { EnergySolarForecasts } from "../../../src/data/energy";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEnergy = (hass: MockHomeAssistant) => {
hass.mockWS("energy/get_prefs", () => ({
energy_sources: [
{
type: "grid",
flow_from: [
{
stat_energy_from: "sensor.energy_consumption_tarif_1",
stat_cost: "sensor.energy_consumption_tarif_1_cost",
entity_energy_from: "sensor.energy_consumption_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_from: "sensor.energy_consumption_tarif_2",
stat_cost: "sensor.energy_consumption_tarif_2_cost",
entity_energy_from: "sensor.energy_consumption_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
flow_to: [
{
stat_energy_to: "sensor.energy_production_tarif_1",
stat_compensation: "sensor.energy_production_tarif_1_compensation",
entity_energy_to: "sensor.energy_production_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_to: "sensor.energy_production_tarif_2",
stat_compensation: "sensor.energy_production_tarif_2_compensation",
entity_energy_to: "sensor.energy_production_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
cost_adjustment_day: 0,
},
{
type: "solar",
stat_energy_from: "sensor.solar_production",
config_entry_solar_forecast: ["solar_forecast"],
},
/* {
type: "battery",
stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input",
}, */
{
type: "gas",
stat_energy_from: "sensor.energy_gas",
stat_cost: "sensor.energy_gas_cost",
entity_energy_from: "sensor.energy_gas",
entity_energy_price: null,
number_energy_price: null,
},
],
device_consumption: [
{
stat_consumption: "sensor.energy_car",
},
{
stat_consumption: "sensor.energy_ac",
},
{
stat_consumption: "sensor.energy_washing_machine",
},
{
stat_consumption: "sensor.energy_dryer",
},
{
stat_consumption: "sensor.energy_heat_pump",
},
{
stat_consumption: "sensor.energy_boiler",
},
],
}));
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
const todayString = format(startOfToday(), "yyyy-MM-dd");
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
hass.mockWS(
"energy/solar_forecast",
(): EnergySolarForecasts => ({
solar_forecast: {
wh_hours: {
[`${todayString}T06:00:00`]: 0,
[`${todayString}T06:23:00`]: 6,
[`${todayString}T06:45:00`]: 39,
[`${todayString}T07:00:00`]: 28,
[`${todayString}T08:00:00`]: 208,
[`${todayString}T09:00:00`]: 352,
[`${todayString}T10:00:00`]: 544,
[`${todayString}T11:00:00`]: 748,
[`${todayString}T12:00:00`]: 1259,
[`${todayString}T13:00:00`]: 1361,
[`${todayString}T14:00:00`]: 1373,
[`${todayString}T15:00:00`]: 1370,
[`${todayString}T16:00:00`]: 1186,
[`${todayString}T17:00:00`]: 937,
[`${todayString}T18:00:00`]: 652,
[`${todayString}T19:00:00`]: 370,
[`${todayString}T20:00:00`]: 155,
[`${todayString}T21:48:00`]: 24,
[`${todayString}T22:36:00`]: 0,
[`${tomorrowString}T06:01:00`]: 0,
[`${tomorrowString}T06:23:00`]: 9,
[`${tomorrowString}T06:45:00`]: 47,
[`${tomorrowString}T07:00:00`]: 48,
[`${tomorrowString}T08:00:00`]: 473,
[`${tomorrowString}T09:00:00`]: 827,
[`${tomorrowString}T10:00:00`]: 1153,
[`${tomorrowString}T11:00:00`]: 1413,
[`${tomorrowString}T12:00:00`]: 1590,
[`${tomorrowString}T13:00:00`]: 1652,
[`${tomorrowString}T14:00:00`]: 1612,
[`${tomorrowString}T15:00:00`]: 1438,
[`${tomorrowString}T16:00:00`]: 1149,
[`${tomorrowString}T17:00:00`]: 830,
[`${tomorrowString}T18:00:00`]: 542,
[`${tomorrowString}T19:00:00`]: 311,
[`${tomorrowString}T20:00:00`]: 140,
[`${tomorrowString}T21:47:00`]: 22,
[`${tomorrowString}T22:34:00`]: 0,
},
},
})
);
};

178
demo/src/stubs/entities.ts Normal file
View File

@@ -0,0 +1,178 @@
import { convertEntities } from "../../../src/fake_data/entity";
export const energyEntities = () =>
convertEntities({
"sensor.grid_fossil_fuel_percentage": {
entity_id: "sensor.grid_fossil_fuel_percentage",
state: "88.6",
attributes: {
unit_of_measurement: "%",
},
},
"sensor.solar_production": {
entity_id: "sensor.solar_production",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Solar",
unit_of_measurement: "kWh",
},
},
"sensor.battery_input": {
entity_id: "sensor.battery_input",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Input",
unit_of_measurement: "kWh",
},
},
"sensor.battery_output": {
entity_id: "sensor.battery_output",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Output",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1": {
entity_id: "sensor.energy_consumption_tarif_1 ",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_2": {
entity_id: "sensor.energy_consumption_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_1": {
entity_id: "sensor.energy_production_tarif_1",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_2": {
entity_id: "sensor.energy_production_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1_cost": {
entity_id: "sensor.energy_consumption_tarif_1_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_consumption_tarif_2_cost": {
entity_id: "sensor.energy_consumption_tarif_2_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_1_compensation": {
entity_id: "sensor.energy_production_tarif_1_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_2_compensation": {
entity_id: "sensor.energy_production_tarif_2_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas_cost": {
entity_id: "sensor.energy_gas_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas": {
entity_id: "sensor.energy_gas",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Gas",
unit_of_measurement: "m³",
},
},
"sensor.energy_car": {
entity_id: "sensor.energy_car",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Electric car",
unit_of_measurement: "kWh",
},
},
"sensor.energy_ac": {
entity_id: "sensor.energy_ac",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Air conditioning",
unit_of_measurement: "kWh",
},
},
"sensor.energy_washing_machine": {
entity_id: "sensor.energy_washing_machine",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Washing machine",
unit_of_measurement: "kWh",
},
},
"sensor.energy_dryer": {
entity_id: "sensor.energy_dryer",
state: "5.5",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Dryer",
unit_of_measurement: "kWh",
},
},
"sensor.energy_heat_pump": {
entity_id: "sensor.energy_heat_pump",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Heat pump",
unit_of_measurement: "kWh",
},
},
"sensor.energy_boiler": {
entity_id: "sensor.energy_boiler",
state: "7",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Boiler",
unit_of_measurement: "kWh",
},
},
});

View File

@@ -1,4 +1,6 @@
import { addHours, differenceInHours, endOfDay } from "date-fns";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams { interface HistoryQueryParams {
@@ -64,6 +66,215 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"]; const incrementalUnits = ["clients", "queries", "ads"];
const generateMeanStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let lastVal = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const delta = Math.random() * maxDiff;
const mean = lastVal + delta;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean,
min: mean - Math.random() * maxDiff,
max: mean + Math.random() * maxDiff,
last_reset: "1970-01-01T00:00:00+00:00",
state: mean,
sum: null,
});
lastVal = mean;
currentDate = addHours(currentDate, 1);
}
return statistics;
};
const generateSumStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum,
});
currentDate = addHours(currentDate, 1);
}
return statistics;
};
const generateCurvedStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number,
metered: boolean
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const hours = differenceInHours(end, start) - 1;
let i = 0;
let half = false;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += i * add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum: metered ? sum : null,
});
currentDate = addHours(currentDate, 1);
if (!half && i > hours / 2) {
half = true;
}
i += half ? -1 : 1;
}
return statistics;
};
const statisticsFunctions: Record<
string,
(id: string, start: Date, end: Date) => StatisticValue[]
> = {
"sensor.energy_consumption_tarif_1": (id: string, start: Date, end: Date) => {
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
const morningLow = generateSumStatistics(id, start, morningEnd, 0, 0.7);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const morningFinalVal = morningLow.length
? morningLow[morningLow.length - 1].sum!
: 0;
const empty = generateSumStatistics(
id,
morningEnd,
eveningStart,
morningFinalVal,
0
);
const eveningLow = generateSumStatistics(
id,
eveningStart,
end,
morningFinalVal,
0.7
);
return [...morningLow, ...empty, ...eveningLow];
},
"sensor.energy_consumption_tarif_2": (id: string, start: Date, end: Date) => {
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const highTarif = generateSumStatistics(
id,
morningEnd,
eveningStart,
0,
0.3
);
const highTarifFinalVal = highTarif.length
? highTarif[highTarif.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, morningEnd, 0, 0);
const evening = generateSumStatistics(
id,
eveningStart,
end,
highTarifFinalVal,
0
);
return [...morning, ...highTarif, ...evening];
},
"sensor.energy_production_tarif_1": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_1_compensation": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_2": (id, start, end) => {
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
0,
0.15,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 1);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.solar_production": (id, start, end) => {
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
0,
0.3,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 2);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
generateMeanStatistics(id, start, end, 35, 1.3),
};
export const mockHistory = (mockHass: MockHomeAssistant) => { export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI( mockHass.mockAPI(
new RegExp("history/period/.+"), new RegExp("history/period/.+"),
@@ -133,4 +344,40 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results; return results;
} }
); );
mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS(
"history/statistics_during_period",
({ statistic_ids, start_time, end_time }, hass) => {
const start = new Date(start_time);
const end = end_time ? new Date(end_time) : new Date();
const statistics: Record<string, StatisticValue[]> = {};
statistic_ids.forEach((id: string) => {
if (id in statisticsFunctions) {
statistics[id] = statisticsFunctions[id](id, start, end);
} else {
const entityState = hass.states[id];
const state = entityState ? Number(entityState.state) : 1;
statistics[id] =
entityState && "last_reset" in entityState.attributes
? generateSumStatistics(
id,
start,
end,
state,
state * (state > 80 ? 0.01 : 0.05)
)
: generateMeanStatistics(
id,
start,
end,
state,
state * (state > 80 ? 0.05 : 0.1)
);
}
});
return statistics;
}
);
}; };

View File

@@ -23,9 +23,9 @@ customElements.whenDefined("hui-view").then(() => {
// eslint-disable-next-line // eslint-disable-next-line
const HUIView = customElements.get("hui-view"); const HUIView = customElements.get("hui-view");
// Patch HUI-VIEW to make the lovelace object available to the demo card // Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView.prototype.createCardElement; const oldCreateCard = HUIView!.prototype.createCardElement;
HUIView.prototype.createCardElement = function (config) { HUIView!.prototype.createCardElement = function (config) {
const el = oldCreateCard.call(this, config); const el = oldCreateCard.call(this, config);
if (el.tagName === "HA-DEMO-CARD") { if (el.tagName === "HA-DEMO-CARD") {
(el as HADemoCard).lovelace = this.lovelace; (el as HADemoCard).lovelace = this.lovelace;

View File

@@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
body: { message: "Template dev tool does not work in the demo." }, body: { message: "Template dev tool does not work in the demo." },
}) })
); );
hass.mockWS("render_template", (msg, onChange) => { hass.mockWS("render_template", (msg, _hass, onChange) => {
onChange!({ onChange!({
result: msg.template, result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false }, listeners: { all: false, domains: [], entities: [], time: false },

View File

@@ -1,3 +1,4 @@
/* eslint-disable lit/no-template-arrow */
import { html, css, LitElement, TemplateResult } from "lit"; import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";

View File

@@ -1,3 +1,4 @@
/* eslint-disable lit/no-template-arrow */
import { html, css, LitElement, TemplateResult } from "lit"; import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph"; import "../../../src/components/trace/hat-script-graph";

View File

@@ -0,0 +1,150 @@
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card";
const alerts: {
title?: string;
description: string | TemplateResult;
type: "info" | "warning" | "error" | "success";
dismissable?: boolean;
action?: string;
rtl?: boolean;
}[] = [
{
title: "Test info alert",
description: "This is a test info alert with a title and description",
type: "info",
},
{
title: "Test warning alert",
description: "This is a test warning alert with a title and description",
type: "warning",
},
{
title: "Test error alert",
description: "This is a test error alert with a title and description",
type: "error",
},
{
title: "Test warning with long string",
description:
"sensor.lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum",
type: "warning",
},
{
title: "Test success alert",
description: "This is a test success alert with a title and description",
type: "success",
},
{
description: "This is a test info alert with description only",
type: "info",
},
{
description:
"This is a test warning alert with a rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really long description only",
type: "warning",
},
{
title: "Error with description and list",
description: html`<p>
This is a test error alert with a title, description and a list
</p>
<ul>
<li>List item #1</li>
<li>List item #2</li>
<li>List item #3</li>
</ul>`,
type: "error",
},
{
title: "Test dismissable alert",
description: "This is a test success alert that can be dismissable",
type: "success",
dismissable: true,
},
{
description: "Dismissable information",
type: "info",
dismissable: true,
},
{
title: "Error with action",
description: "This is a test error alert with action",
type: "error",
action: "restart",
},
{
title: "Unsaved data",
description: "You have unsaved data",
type: "warning",
action: "save",
},
{
description: "Dismissable information (RTL)",
type: "info",
dismissable: true,
rtl: true,
},
{
title: "Error with action",
description: "This is a test error alert with action (RTL)",
type: "error",
action: "restart",
rtl: true,
},
{
title: "Test success alert (RTL)",
description: "This is a test success alert with a title and description",
type: "success",
rtl: true,
},
];
@customElement("demo-ha-alert")
export class DemoHaAlert extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card header="ha-alert demo">
${alerts.map(
(alert) => html`
<ha-alert
.title=${alert.title || ""}
.alertType=${alert.type}
.dismissable=${alert.dismissable || false}
.actionText=${alert.action || ""}
.rtl=${alert.rtl || false}
>
${alert.description}
</ha-alert>
`
)}
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.condition {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
span {
margin-right: 16px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-alert": DemoHaAlert;
}
}

View File

@@ -1,5 +1,5 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { html, LitElement, TemplateResult } from "lit"; import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators"; import { customElement } from "lit/decorators";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { ActionHandlerEvent } from "../../../src/data/lovelace"; import { ActionHandlerEvent } from "../../../src/data/lovelace";
@@ -9,7 +9,6 @@ import { actionHandler } from "../../../src/panels/lovelace/common/directives/ac
export class DemoUtilLongPress extends LitElement { export class DemoUtilLongPress extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
${this.renderStyle()}
${[1, 2, 3].map( ${[1, 2, 3].map(
() => html` () => html`
<ha-card> <ha-card>
@@ -41,26 +40,22 @@ export class DemoUtilLongPress extends LitElement {
area.scrollTop = area.scrollHeight; area.scrollTop = area.scrollHeight;
} }
private renderStyle() { static styles = css`
return html` ha-card {
<style> width: 200px;
ha-card { margin: calc(42vh - 140px) auto;
width: 200px; padding: 8px;
margin: calc(42vh - 140px) auto; text-align: center;
padding: 8px; }
text-align: center; ha-card:first-of-type {
} margin-top: 16px;
ha-card:first-of-type { }
margin-top: 16px; ha-card:last-of-type {
} margin-bottom: 16px;
ha-card:last-of-type { }
margin-bottom: 16px;
}
textarea { textarea {
height: 50px; height: 50px;
} }
</style> `;
`;
}
} }

View File

@@ -172,6 +172,14 @@ class HaGallery extends PolymerElement {
this.$.notifications.showDialog({ message: ev.detail.message }) this.$.notifications.showDialog({ message: ev.detail.message })
); );
this.addEventListener("alert-dismissed-clicked", () =>
this.$.notifications.showDialog({ message: "Alert dismissed clicked" })
);
this.addEventListener("alert-action-clicked", () =>
this.$.notifications.showDialog({ message: "Alert action clicked" })
);
this.addEventListener("hass-more-info", (ev) => { this.addEventListener("hass-more-info", (ev) => {
if (ev.detail.entityId) { if (ev.detail.entityId) {
this.$.notifications.showDialog({ this.$.notifications.showDialog({

View File

@@ -13,6 +13,7 @@ import {
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import "web-animations-js/web-animations-next-lite.min"; import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { import {
HassioAddonDetails, HassioAddonDetails,
@@ -53,7 +54,9 @@ class HassioAddonAudio extends LitElement {
.header=${this.supervisor.localize("addon.configuration.audio.header")} .header=${this.supervisor.localize("addon.configuration.audio.header")}
> >
<div class="card-content"> <div class="card-content">
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<paper-dropdown-menu <paper-dropdown-menu
.label=${this.supervisor.localize( .label=${this.supervisor.localize(
@@ -117,10 +120,6 @@ class HassioAddonAudio extends LitElement {
paper-dropdown-menu { paper-dropdown-menu {
display: block; display: block;
} }
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
paper-item { paper-item {
width: 450px; width: 450px;
} }

View File

@@ -17,6 +17,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu"; import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-form/ha-form"; import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/ha-form"; import type { HaFormSchema } from "../../../../src/components/ha-form/ha-form";
import "../../../../src/components/ha-formfield"; import "../../../../src/components/ha-formfield";
@@ -135,17 +136,19 @@ class HassioAddonConfig extends LitElement {
@value-changed=${this._configChanged} @value-changed=${this._configChanged}
.yamlSchema=${ADDON_YAML_SCHEMA} .yamlSchema=${ADDON_YAML_SCHEMA}
></ha-yaml-editor>`} ></ha-yaml-editor>`}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${!this._yamlMode || ${!this._yamlMode ||
(this._canShowSchema && this.addon.schema) || (this._canShowSchema && this.addon.schema) ||
this._valid this._valid
? "" ? ""
: html` : html`
<div class="errors"> <ha-alert alert-type="error">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.configuration.options.invalid_yaml" "addon.configuration.options.invalid_yaml"
)} )}
</div> </ha-alert>
`} `}
</div> </div>
${hasHiddenOptions ${hasHiddenOptions
@@ -256,7 +259,7 @@ class HassioAddonConfig extends LitElement {
path: "options", path: "options",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.common.update_available", "addon.common.update_available",
"error", "error",
@@ -297,7 +300,7 @@ class HassioAddonConfig extends LitElement {
if (this.addon?.state === "started") { if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
} }
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -324,13 +327,7 @@ class HassioAddonConfig extends LitElement {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.errors {
color: var(--error-color);
margin-top: 16px;
}
.syntaxerror {
color: var(--error-color);
}
.card-menu { .card-menu {
float: right; float: right;
z-index: 3; z-index: 3;

View File

@@ -10,6 +10,7 @@ import {
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { import {
HassioAddonDetails, HassioAddonDetails,
@@ -62,7 +63,9 @@ class HassioAddonNetwork extends LitElement {
)} )}
> >
<div class="card-content"> <div class="card-content">
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<table> <table>
<tbody> <tbody>
@@ -86,9 +89,9 @@ class HassioAddonNetwork extends LitElement {
<td> <td>
<paper-input <paper-input
@value-changed=${this._configChanged} @value-changed=${this._configChanged}
placeholder="${this.supervisor.localize( placeholder=${this.supervisor.localize(
"addon.configuration.network.disabled" "addon.configuration.network.disabled"
)}" )}
.value=${item.host ? String(item.host) : ""} .value=${item.host ? String(item.host) : ""}
.container=${item.container} .container=${item.container}
no-label-float no-label-float
@@ -168,7 +171,7 @@ class HassioAddonNetwork extends LitElement {
if (this.addon?.state === "started") { if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
} }
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_reset", "addon.failed_to_reset",
"error", "error",
@@ -204,7 +207,7 @@ class HassioAddonNetwork extends LitElement {
if (this.addon?.state === "started") { if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
} }
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -225,10 +228,6 @@ class HassioAddonNetwork extends LitElement {
ha-card { ha-card {
display: block; display: block;
} }
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.card-actions { .card-actions {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@@ -1,5 +1,6 @@
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
@@ -38,7 +39,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
return html` return html`
<div class="content"> <div class="content">
<ha-card> <ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<div class="card-content"> <div class="card-content">
${this._content ${this._content
? html`<ha-markdown .content=${this._content}></ha-markdown>` ? html`<ha-markdown .content=${this._content}></ha-markdown>`
@@ -76,7 +79,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
this.hass, this.hass,
this.addon!.slug this.addon!.slug
); );
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.documentation.get_logs", "addon.documentation.get_logs",
"error", "error",

View File

@@ -222,7 +222,7 @@ class HassioAddonDashboard extends LitElement {
try { try {
const addoninfo = await fetchHassioAddonInfo(this.hass, addon); const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo; this.addon = addoninfo;
} catch (err) { } catch (err: any) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`; this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined; this.addon = undefined;
} }

View File

@@ -23,6 +23,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { navigate } from "../../../../src/common/navigate"; import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/buttons/ha-call-api-button"; import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge"; import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
@@ -122,18 +123,18 @@ class HassioAddonInfo extends LitElement {
<div class="card-content"> <div class="card-content">
<hassio-card-content <hassio-card-content
.hass=${this.hass} .hass=${this.hass}
.title="${this.supervisor.localize( .title=${this.supervisor.localize(
"addon.dashboard.new_update_available", "addon.dashboard.new_update_available",
"name", "name",
this.addon.name, this.addon.name,
"version", "version",
this.addon.version_latest this.addon.version_latest
)}" )}
.description="${this.supervisor.localize( .description=${this.supervisor.localize(
"common.running_version", "common.running_version",
"version", "version",
this.addon.version this.addon.version
)}" )}
icon=${mdiArrowUpBoldCircle} icon=${mdiArrowUpBoldCircle}
iconClass="update" iconClass="update"
></hassio-card-content> ></hassio-card-content>
@@ -143,14 +144,14 @@ class HassioAddonInfo extends LitElement {
this.addon.arch this.addon.arch
) )
? html` ? html`
<p class="warning"> <ha-alert alert-type="warning">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.not_available_arch" "addon.dashboard.not_available_arch"
)} )}
</p> </ha-alert>
` `
: html` : html`
<p class="warning"> <ha-alert alert-type="warning">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.not_available_arch", "addon.dashboard.not_available_arch",
"core_version_installed", "core_version_installed",
@@ -158,7 +159,7 @@ class HassioAddonInfo extends LitElement {
"core_version_needed", "core_version_needed",
addonStoreInfo.homeassistant addonStoreInfo.homeassistant
)} )}
</p> </ha-alert>
` `
: ""} : ""}
</div> </div>
@@ -253,7 +254,7 @@ class HassioAddonInfo extends LitElement {
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.visit_addon_page", "addon.dashboard.visit_addon_page",
"name", "name",
html`<a href="${this.addon.url!}" target="_blank" rel="noreferrer" html`<a href=${this.addon.url!} target="_blank" rel="noreferrer"
>${this.addon.name}</a >${this.addon.name}</a
>` >`
)} )}
@@ -436,10 +437,10 @@ class HassioAddonInfo extends LitElement {
${this.addon.version ${this.addon.version
? html` ? html`
<div <div
class="${classMap({ class=${classMap({
"addon-options": true, "addon-options": true,
started: this.addon.state === "started", started: this.addon.state === "started",
})}" })}
> >
<ha-settings-row ?three-line=${this.narrow}> <ha-settings-row ?three-line=${this.narrow}>
<span slot="heading"> <span slot="heading">
@@ -569,21 +570,23 @@ class HassioAddonInfo extends LitElement {
: ""} : ""}
</div> </div>
</div> </div>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${!this.addon.version && addonStoreInfo && !this.addon.available ${!this.addon.version && addonStoreInfo && !this.addon.available
? !addonArchIsSupported( ? !addonArchIsSupported(
this.supervisor.info.supported_arch, this.supervisor.info.supported_arch,
this.addon.arch this.addon.arch
) )
? html` ? html`
<p class="warning"> <ha-alert alert-type="warning">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.not_available_arch" "addon.dashboard.not_available_arch"
)} )}
</p> </ha-alert>
` `
: html` : html`
<p class="warning"> <ha-alert alert-type="warning">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.not_available_version", "addon.dashboard.not_available_version",
"core_version_installed", "core_version_installed",
@@ -591,7 +594,7 @@ class HassioAddonInfo extends LitElement {
"core_version_needed", "core_version_needed",
addonStoreInfo!.homeassistant addonStoreInfo!.homeassistant
)} )}
</p> </ha-alert>
` `
: ""} : ""}
</div> </div>
@@ -793,7 +796,7 @@ class HassioAddonInfo extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -815,7 +818,7 @@ class HassioAddonInfo extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -837,7 +840,7 @@ class HassioAddonInfo extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -859,7 +862,7 @@ class HassioAddonInfo extends LitElement {
path: "security", path: "security",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -881,7 +884,7 @@ class HassioAddonInfo extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -909,7 +912,7 @@ class HassioAddonInfo extends LitElement {
title: this.supervisor.localize("addon.dashboard.changelog"), title: this.supervisor.localize("addon.dashboard.changelog"),
content, content,
}); });
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"addon.dashboard.action_error.get_changelog" "addon.dashboard.action_error.get_changelog"
@@ -931,7 +934,7 @@ class HassioAddonInfo extends LitElement {
path: "install", path: "install",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.install"), title: this.supervisor.localize("addon.dashboard.action_error.install"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -952,7 +955,7 @@ class HassioAddonInfo extends LitElement {
path: "stop", path: "stop",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.stop"), title: this.supervisor.localize("addon.dashboard.action_error.stop"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -973,7 +976,7 @@ class HassioAddonInfo extends LitElement {
path: "stop", path: "stop",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.restart"), title: this.supervisor.localize("addon.dashboard.action_error.restart"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -987,7 +990,7 @@ class HassioAddonInfo extends LitElement {
supervisor: this.supervisor, supervisor: this.supervisor,
name: this.addon.name, name: this.addon.name,
version: this.addon.version_latest, version: this.addon.version_latest,
snapshotParams: { backupParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`, name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug], addons: [this.addon.slug],
homeassistant: false, homeassistant: false,
@@ -1032,7 +1035,7 @@ class HassioAddonInfo extends LitElement {
button.progress = false; button.progress = false;
return; return;
} }
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to validate addon configuration", title: "Failed to validate addon configuration",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -1050,7 +1053,7 @@ class HassioAddonInfo extends LitElement {
path: "start", path: "start",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.start"), title: this.supervisor.localize("addon.dashboard.action_error.start"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -1088,7 +1091,7 @@ class HassioAddonInfo extends LitElement {
path: "uninstall", path: "uninstall",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"addon.dashboard.action_error.uninstall" "addon.dashboard.action_error.uninstall"
@@ -1149,6 +1152,7 @@ class HassioAddonInfo extends LitElement {
margin-bottom: 16px; margin-bottom: 16px;
} }
img.logo { img.logo {
max-width: 100%;
max-height: 60px; max-height: 60px;
margin: 16px 0; margin: 16px 0;
display: block; display: block;
@@ -1158,10 +1162,10 @@ class HassioAddonInfo extends LitElement {
display: flex; display: flex;
} }
ha-svg-icon.running { ha-svg-icon.running {
color: var(--paper-green-400); color: var(--success-color);
} }
ha-svg-icon.stopped { ha-svg-icon.stopped {
color: var(--google-red-300); color: var(--error-color);
} }
ha-call-api-button { ha-call-api-button {
font-weight: 500; font-weight: 500;

View File

@@ -1,6 +1,7 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { import {
fetchHassioAddonLogs, fetchHassioAddonLogs,
@@ -34,7 +35,9 @@ class HassioAddonLogs extends LitElement {
return html` return html`
<h1>${this.addon.name}</h1> <h1>${this.addon.name}</h1>
<ha-card> <ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<div class="card-content"> <div class="card-content">
${this._content ${this._content
? html`<hassio-ansi-to-html ? html`<hassio-ansi-to-html
@@ -60,10 +63,6 @@ class HassioAddonLogs extends LitElement {
ha-card { ha-card {
display: block; display: block;
} }
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
`, `,
]; ];
} }
@@ -72,7 +71,7 @@ class HassioAddonLogs extends LitElement {
this._error = undefined; this._error = undefined;
try { try {
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug); this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.logs.get_logs", "addon.logs.get_logs",
"error", "error",

View File

@@ -14,7 +14,7 @@ import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import relativeTime from "../../../src/common/datetime/relative_time"; import { relativeTime } from "../../../src/common/datetime/relative_time";
import { HASSDomEvent } from "../../../src/common/dom/fire_event"; import { HASSDomEvent } from "../../../src/common/dom/fire_event";
import { import {
DataTableColumnContainer, DataTableColumnContainer,
@@ -25,12 +25,12 @@ import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab"; import "../../../src/components/ha-fab";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
fetchHassioSnapshots, fetchHassioBackups,
friendlyFolderName, friendlyFolderName,
HassioSnapshot, HassioBackup,
reloadHassioSnapshots, reloadHassioBackups,
removeSnapshot, removeBackup,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import {
showAlertDialog, showAlertDialog,
@@ -40,14 +40,14 @@ import "../../../src/layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table"; import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot"; import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload"; import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-snapshots") @customElement("hassio-backups")
export class HassioSnapshots extends LitElement { export class HassioBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@@ -58,9 +58,9 @@ export class HassioSnapshots extends LitElement {
@property({ type: Boolean }) public isWide!: boolean; @property({ type: Boolean }) public isWide!: boolean;
@state() private _selectedSnapshots: string[] = []; @state() private _selectedBackups: string[] = [];
@state() private _snapshots?: HassioSnapshot[] = []; @state() private _backups?: HassioBackup[] = [];
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
@@ -75,26 +75,26 @@ export class HassioSnapshots extends LitElement {
} }
public async refreshData() { public async refreshData() {
await reloadHassioSnapshots(this.hass); await reloadHassioBackups(this.hass);
await this.fetchSnapshots(); await this.fetchBackups();
} }
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => { private _computeBackupContent = (backup: HassioBackup): string => {
if (snapshot.type === "full") { if (backup.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot"); return this.supervisor.localize("backup.full_backup");
} }
const content: string[] = []; const content: string[] = [];
if (snapshot.content.homeassistant) { if (backup.content.homeassistant) {
content.push("Home Assistant"); content.push("Home Assistant");
} }
if (snapshot.content.folders.length !== 0) { if (backup.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) { for (const folder of backup.content.folders) {
content.push(friendlyFolderName[folder] || folder); content.push(friendlyFolderName[folder] || folder);
} }
} }
if (snapshot.content.addons.length !== 0) { if (backup.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) { for (const addon of backup.content.addons) {
content.push( content.push(
this.supervisor.supervisor.addons.find( this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon (entry) => entry.slug === addon
@@ -117,23 +117,23 @@ export class HassioSnapshots extends LitElement {
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({ (narrow: boolean): DataTableColumnContainer => ({
name: { name: {
title: this.supervisor?.localize("snapshot.name") || "", title: this.supervisor?.localize("backup.name") || "",
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, grows: true,
template: (entry: string, snapshot: any) => template: (entry: string, backup: any) =>
html`${entry || snapshot.slug} html`${entry || backup.slug}
<div class="secondary">${snapshot.secondary}</div>`, <div class="secondary">${backup.secondary}</div>`,
}, },
date: { date: {
title: this.supervisor?.localize("snapshot.created") || "", title: this.supervisor?.localize("backup.created") || "",
width: "15%", width: "15%",
direction: "desc", direction: "desc",
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
template: (entry: string) => template: (entry: string) =>
relativeTime(new Date(entry), this.hass.localize), relativeTime(new Date(entry), this.hass.locale),
}, },
secondary: { secondary: {
title: "", title: "",
@@ -143,10 +143,10 @@ export class HassioSnapshots extends LitElement {
}) })
); );
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) => private _backupData = memoizeOne((backups: HassioBackup[]) =>
snapshots.map((snapshot) => ({ backups.map((backup) => ({
...snapshot, ...backup,
secondary: this._computeSnapshotContent(snapshot), secondary: this._computeBackupContent(backup),
})) }))
); );
@@ -160,11 +160,11 @@ export class HassioSnapshots extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.localizeFunc=${this.supervisor.localize} .localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")} .searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")} .noDataText=${this.supervisor.localize("backup.no_backups")}
.narrow=${this.narrow} .narrow=${this.narrow}
.route=${this.route} .route=${this.route}
.columns=${this._columns(this.narrow)} .columns=${this._columns(this.narrow)}
.data=${this._snapshotData(this._snapshots || [])} .data=${this._backupData(this._backups || [])}
id="slug" id="slug"
@row-click=${this._handleRowClicked} @row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged} @selection-changed=${this._handleSelectionChanged}
@@ -187,12 +187,12 @@ export class HassioSnapshots extends LitElement {
</mwc-list-item> </mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116) ${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item> ? html`<mwc-list-item>
${this.supervisor?.localize("snapshot.upload_snapshot")} ${this.supervisor?.localize("backup.upload_backup")}
</mwc-list-item>` </mwc-list-item>`
: ""} : ""}
</ha-button-menu> </ha-button-menu>
${this._selectedSnapshots.length ${this._selectedBackups.length
? html`<div ? html`<div
class=${classMap({ class=${classMap({
"header-toolbar": this.narrow, "header-toolbar": this.narrow,
@@ -201,8 +201,8 @@ export class HassioSnapshots extends LitElement {
slot="header" slot="header"
> >
<p class="selected-txt"> <p class="selected-txt">
${this.supervisor.localize("snapshot.selected", { ${this.supervisor.localize("backup.selected", {
number: this._selectedSnapshots.length, number: this._selectedBackups.length,
})} })}
</p> </p>
<div class="header-btns"> <div class="header-btns">
@@ -212,7 +212,7 @@ export class HassioSnapshots extends LitElement {
@click=${this._deleteSelected} @click=${this._deleteSelected}
class="warning" class="warning"
> >
${this.supervisor.localize("snapshot.delete_selected")} ${this.supervisor.localize("backup.delete_selected")}
</mwc-button> </mwc-button>
` `
: html` : html`
@@ -224,7 +224,7 @@ export class HassioSnapshots extends LitElement {
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon> <ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn"> <paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("snapshot.delete_selected")} ${this.supervisor.localize("backup.delete_selected")}
</paper-tooltip> </paper-tooltip>
`} `}
</div> </div>
@@ -233,8 +233,8 @@ export class HassioSnapshots extends LitElement {
<ha-fab <ha-fab
slot="fab" slot="fab"
@click=${this._createSnapshot} @click=${this._createBackup}
.label=${this.supervisor.localize("snapshot.create_snapshot")} .label=${this.supervisor.localize("backup.create_backup")}
extended extended
> >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
@@ -249,7 +249,7 @@ export class HassioSnapshots extends LitElement {
this.refreshData(); this.refreshData();
break; break;
case 1: case 1:
this._showUploadSnapshotDialog(); this._showUploadBackupDialog();
break; break;
} }
} }
@@ -257,33 +257,33 @@ export class HassioSnapshots extends LitElement {
private _handleSelectionChanged( private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent> ev: HASSDomEvent<SelectionChangedEvent>
): void { ): void {
this._selectedSnapshots = ev.detail.value; this._selectedBackups = ev.detail.value;
} }
private _showUploadSnapshotDialog() { private _showUploadBackupDialog() {
showSnapshotUploadDialog(this, { showBackupUploadDialog(this, {
showSnapshot: (slug: string) => showBackup: (slug: string) =>
showHassioSnapshotDialog(this, { showHassioBackupDialog(this, {
slug, slug,
supervisor: this.supervisor, supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(), onDelete: () => this.fetchBackups(),
}), }),
reloadSnapshot: () => this.refreshData(), reloadBackup: () => this.refreshData(),
}); });
} }
private async fetchSnapshots() { private async fetchBackups() {
await reloadHassioSnapshots(this.hass); await reloadHassioBackups(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass); this._backups = await fetchHassioBackups(this.hass);
} }
private async _deleteSelected() { private async _deleteSelected() {
const confirm = await showConfirmationDialog(this, { const confirm = await showConfirmationDialog(this, {
title: this.supervisor.localize("snapshot.delete_snapshot_title"), title: this.supervisor.localize("backup.delete_backup_title"),
text: this.supervisor.localize("snapshot.delete_snapshot_text", { text: this.supervisor.localize("backup.delete_backup_text", {
number: this._selectedSnapshots.length, number: this._selectedBackups.length,
}), }),
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"), confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
}); });
if (!confirm) { if (!confirm) {
@@ -292,44 +292,44 @@ export class HassioSnapshots extends LitElement {
try { try {
await Promise.all( await Promise.all(
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug)) this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
); );
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("snapshot.failed_to_delete"), title: this.supervisor.localize("backup.failed_to_delete"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
return; return;
} }
await reloadHassioSnapshots(this.hass); await reloadHassioBackups(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass); this._backups = await fetchHassioBackups(this.hass);
this._dataTable.clearSelection(); this._dataTable.clearSelection();
} }
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) { private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id; const slug = ev.detail.id;
showHassioSnapshotDialog(this, { showHassioBackupDialog(this, {
slug, slug,
supervisor: this.supervisor, supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(), onDelete: () => this.fetchBackups(),
}); });
} }
private _createSnapshot() { private _createBackup() {
if (this.supervisor!.info.state !== "running") { if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor!.localize("snapshot.could_not_create"), title: this.supervisor!.localize("backup.could_not_create"),
text: this.supervisor!.localize( text: this.supervisor!.localize(
"snapshot.create_blocked_not_running", "backup.create_blocked_not_running",
"state", "state",
this.supervisor!.info.state this.supervisor!.info.state
), ),
}); });
return; return;
} }
showHassioCreateSnapshotDialog(this, { showHassioCreateBackupDialog(this, {
supervisor: this.supervisor!, supervisor: this.supervisor!,
onCreate: () => this.fetchSnapshots(), onCreate: () => this.fetchBackups(),
}); });
} }
@@ -378,6 +378,6 @@ export class HassioSnapshots extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"hassio-snapshots": HassioSnapshots; "hassio-backups": HassioBackups;
} }
} }

View File

@@ -41,16 +41,16 @@ class HassioAnsiToHtml extends LitElement {
text-decoration: underline line-through; text-decoration: underline line-through;
} }
.fg-red { .fg-red {
color: rgb(222, 56, 43); color: var(--error-color);
} }
.fg-green { .fg-green {
color: rgb(57, 181, 74); color: var(--success-color);
} }
.fg-yellow { .fg-yellow {
color: rgb(255, 199, 6); color: var(--warning-color);
} }
.fg-blue { .fg-blue {
color: rgb(0, 111, 184); color: var(--info-color);
} }
.fg-magenta { .fg-magenta {
color: rgb(118, 38, 113); color: rgb(118, 38, 113);
@@ -65,16 +65,16 @@ class HassioAnsiToHtml extends LitElement {
background-color: rgb(0, 0, 0); background-color: rgb(0, 0, 0);
} }
.bg-red { .bg-red {
background-color: rgb(222, 56, 43); background-color: var(--error-color);
} }
.bg-green { .bg-green {
background-color: rgb(57, 181, 74); background-color: var(--success-color);
} }
.bg-yellow { .bg-yellow {
background-color: rgb(255, 199, 6); background-color: var(--warning-color);
} }
.bg-blue { .bg-blue {
background-color: rgb(0, 111, 184); background-color: var(--info-color);
} }
.bg-magenta { .bg-magenta {
background-color: rgb(118, 38, 113); background-color: rgb(118, 38, 113);

View File

@@ -37,7 +37,7 @@ class HassioCardContent extends LitElement {
${this.iconImage ${this.iconImage
? html` ? html`
<div class="icon_image ${this.iconClass}"> <div class="icon_image ${this.iconClass}">
<img src="${this.iconImage}" .title=${this.iconTitle} /> <img src=${this.iconImage} .title=${this.iconTitle} />
<div></div> <div></div>
</div> </div>
` `
@@ -80,14 +80,14 @@ class HassioCardContent extends LitElement {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-svg-icon.update { ha-svg-icon.update {
color: var(--paper-orange-400); color: var(--warning-color);
} }
ha-svg-icon.running, ha-svg-icon.running,
ha-svg-icon.installed { ha-svg-icon.installed {
color: var(--paper-green-400); color: var(--success-color);
} }
ha-svg-icon.hassupdate, ha-svg-icon.hassupdate,
ha-svg-icon.snapshot { ha-svg-icon.backup {
color: var(--paper-item-icon-color); color: var(--paper-item-icon-color);
} }
ha-svg-icon.not_available { ha-svg-icon.not_available {
@@ -122,7 +122,7 @@ class HassioCardContent extends LitElement {
} }
.dot { .dot {
position: absolute; position: absolute;
background-color: var(--paper-orange-400); background-color: var(--warning-color);
width: 12px; width: 12px;
height: 12px; height: 12px;
top: 8px; top: 8px;

View File

@@ -8,23 +8,20 @@ import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload"; import "../../../src/components/ha-file-upload";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
HassioSnapshot,
uploadSnapshot,
} from "../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
"snapshot-uploaded": { snapshot: HassioSnapshot }; "backup-uploaded": { backup: HassioBackup };
} }
} }
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-snapshot") @customElement("hassio-upload-backup")
export class HassioUploadSnapshot extends LitElement { export class HassioUploadBackup extends LitElement {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@state() public value: string | null = null; @state() public value: string | null = null;
@@ -37,7 +34,7 @@ export class HassioUploadSnapshot extends LitElement {
.uploading=${this._uploading} .uploading=${this._uploading}
.icon=${mdiFolderUpload} .icon=${mdiFolderUpload}
accept="application/x-tar" accept="application/x-tar"
label="Upload snapshot" label="Upload backup"
@file-picked=${this._uploadFile} @file-picked=${this._uploadFile}
auto-open-file-dialog auto-open-file-dialog
></ha-file-upload> ></ha-file-upload>
@@ -49,10 +46,10 @@ export class HassioUploadSnapshot extends LitElement {
if (file.size > MAX_FILE_SIZE) { if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Snapshot file is too big", title: "Backup file is too big",
text: html`The maximum allowed filesize is 1GB.<br /> text: html`The maximum allowed filesize is 1GB.<br />
<a <a
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-snapshot-on-a-new-install" href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-backup-on-a-new-install"
target="_blank" target="_blank"
>Have a look here on how to restore it.</a >Have a look here on how to restore it.</a
>`, >`,
@@ -64,16 +61,16 @@ export class HassioUploadSnapshot extends LitElement {
if (!["application/x-tar"].includes(file.type)) { if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Unsupported file format", title: "Unsupported file format",
text: "Please choose a Home Assistant snapshot file (.tar)", text: "Please choose a Home Assistant backup file (.tar)",
confirmText: "ok", confirmText: "ok",
}); });
return; return;
} }
this._uploading = true; this._uploading = true;
try { try {
const snapshot = await uploadSnapshot(this.hass, file); const backup = await uploadBackup(this.hass, file);
fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data }); fireEvent(this, "backup-uploaded", { backup: backup.data });
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Upload failed", title: "Upload failed",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -87,6 +84,6 @@ export class HassioUploadSnapshot extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"hassio-upload-snapshot": HassioUploadSnapshot; "hassio-upload-backup": HassioUploadBackup;
} }
} }

View File

@@ -11,10 +11,10 @@ import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio"; import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio"; import type { HaRadio } from "../../../src/components/ha-radio";
import { import {
HassioFullSnapshotCreateParams, HassioFullBackupCreateParams,
HassioPartialSnapshotCreateParams, HassioPartialBackupCreateParams,
HassioSnapshotDetail, HassioBackupDetail,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types"; import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@@ -64,17 +64,17 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
})) }))
.sort((a, b) => (a.name > b.name ? 1 : -1)); .sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-snapshot-content") @customElement("supervisor-backup-content")
export class SupervisorSnapshotContent extends LitElement { export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc; @property() public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor; @property({ attribute: false }) public supervisor?: Supervisor;
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail; @property({ attribute: false }) public backup?: HassioBackupDetail;
@property() public snapshotType: HassioSnapshotDetail["type"] = "full"; @property() public backupType: HassioBackupDetail["type"] = "full";
@property({ attribute: false }) public folders?: CheckboxItem[]; @property({ attribute: false }) public folders?: CheckboxItem[];
@@ -82,37 +82,35 @@ export class SupervisorSnapshotContent extends LitElement {
@property({ type: Boolean }) public homeAssistant = false; @property({ type: Boolean }) public homeAssistant = false;
@property({ type: Boolean }) public snapshotHasPassword = false; @property({ type: Boolean }) public backupHasPassword = false;
@property({ type: Boolean }) public onboarding = false; @property({ type: Boolean }) public onboarding = false;
@property() public snapshotName = ""; @property() public backupName = "";
@property() public snapshotPassword = ""; @property() public backupPassword = "";
@property() public confirmSnapshotPassword = ""; @property() public confirmBackupPassword = "";
public willUpdate(changedProps) { public willUpdate(changedProps) {
super.willUpdate(changedProps); super.willUpdate(changedProps);
if (!this.hasUpdated) { if (!this.hasUpdated) {
this.folders = _computeFolders( this.folders = _computeFolders(
this.snapshot this.backup
? this.snapshot.folders ? this.backup.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"] : ["homeassistant", "ssl", "share", "media", "addons/local"]
); );
this.addons = _computeAddons( this.addons = _computeAddons(
this.snapshot this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
? this.snapshot.addons
: this.supervisor?.supervisor.addons
); );
this.snapshotType = this.snapshot?.type || "full"; this.backupType = this.backup?.type || "full";
this.snapshotName = this.snapshot?.name || ""; this.backupName = this.backup?.name || "";
this.snapshotHasPassword = this.snapshot?.protected || false; this.backupHasPassword = this.backup?.protected || false;
} }
} }
private _localize = (string: string) => private _localize = (string: string) =>
this.supervisor?.localize(`snapshot.${string}`) || this.supervisor?.localize(`backup.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`); this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -120,72 +118,70 @@ export class SupervisorSnapshotContent extends LitElement {
return html``; return html``;
} }
const foldersSection = const foldersSection =
this.snapshotType === "partial" ? this._getSection("folders") : undefined; this.backupType === "partial" ? this._getSection("folders") : undefined;
const addonsSection = const addonsSection =
this.snapshotType === "partial" ? this._getSection("addons") : undefined; this.backupType === "partial" ? this._getSection("addons") : undefined;
return html` return html`
${this.snapshot ${this.backup
? html`<div class="details"> ? html`<div class="details">
${this.snapshot.type === "full" ${this.backup.type === "full"
? this._localize("full_snapshot") ? this._localize("full_backup")
: this._localize("partial_snapshot")} : this._localize("partial_backup")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br /> (${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br />
${this.hass ${this.hass
? formatDateTime(new Date(this.snapshot.date), this.hass.locale) ? formatDateTime(new Date(this.backup.date), this.hass.locale)
: this.snapshot.date} : this.backup.date}
</div>` </div>`
: html`<paper-input : html`<paper-input
name="snapshotName" name="backupName"
.label=${this.supervisor?.localize("snapshot.name") || "Name"} .label=${this._localize("name")}
.value=${this.snapshotName} .value=${this.backupName}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input>`} </paper-input>`}
${!this.snapshot || this.snapshot.type === "full" ${!this.backup || this.backup.type === "full"
? html`<div class="sub-header"> ? html`<div class="sub-header">
${!this.snapshot ${!this.backup
? this._localize("type") ? this._localize("type")
: this._localize("select_type")} : this._localize("select_type")}
</div> </div>
<div class="snapshot-types"> <div class="backup-types">
<ha-formfield .label=${this._localize("full_snapshot")}> <ha-formfield .label=${this._localize("full_backup")}>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="full" value="full"
name="snapshotType" name="backupType"
.checked=${this.snapshotType === "full"} .checked=${this.backupType === "full"}
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
<ha-formfield .label=${this._localize("partial_snapshot")}> <ha-formfield .label=${this._localize("partial_backup")}>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="partial" value="partial"
name="snapshotType" name="backupType"
.checked=${this.snapshotType === "partial"} .checked=${this.backupType === "partial"}
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
</div>` </div>`
: ""} : ""}
${this.snapshotType === "partial" ${this.backupType === "partial"
? html`<div class="partial-picker"> ? html`<div class="partial-picker">
${this.snapshot && this.snapshot.homeassistant ${this.backup && this.backup.homeassistant
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
label="Home Assistant" label="Home Assistant"
.iconPath=${mdiHomeAssistant} .iconPath=${mdiHomeAssistant}
.version=${this.snapshot.homeassistant} .version=${this.backup.homeassistant}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
> >
<ha-checkbox <ha-checkbox
.checked=${this.homeAssistant} .checked=${this.homeAssistant}
@click=${() => { @click=${this.toggleHomeAssistant}
this.homeAssistant = !this.homeAssistant;
}}
> >
</ha-checkbox> </ha-checkbox>
</ha-formfield> </ha-formfield>
@@ -233,38 +229,38 @@ export class SupervisorSnapshotContent extends LitElement {
: ""} : ""}
</div> ` </div> `
: ""} : ""}
${this.snapshotType === "partial" && ${this.backupType === "partial" &&
(!this.snapshot || this.snapshotHasPassword) (!this.backup || this.backupHasPassword)
? html`<hr />` ? html`<hr />`
: ""} : ""}
${!this.snapshot ${!this.backup
? html`<ha-formfield ? html`<ha-formfield
class="password" class="password"
.label=${this._localize("password_protection")} .label=${this._localize("password_protection")}
> >
<ha-checkbox <ha-checkbox
.checked=${this.snapshotHasPassword} .checked=${this.backupHasPassword}
@change=${this._toggleHasPassword} @change=${this._toggleHasPassword}
> >
</ha-checkbox> </ha-checkbox>
</ha-formfield>` </ha-formfield>`
: ""} : ""}
${this.snapshotHasPassword ${this.backupHasPassword
? html` ? html`
<paper-input <paper-input
.label=${this._localize("password")} .label=${this._localize("password")}
type="password" type="password"
name="snapshotPassword" name="backupPassword"
.value=${this.snapshotPassword} .value=${this.backupPassword}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input> </paper-input>
${!this.snapshot ${!this.backup
? html` <paper-input ? html` <paper-input
.label=${this.supervisor?.localize("confirm_password")} .label=${this._localize("confirm_password")}
type="password" type="password"
name="confirmSnapshotPassword" name="confirmBackupPassword"
.value=${this.confirmSnapshotPassword} .value=${this.confirmBackupPassword}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input>` </paper-input>`
@@ -274,6 +270,10 @@ export class SupervisorSnapshotContent extends LitElement {
`; `;
} }
private toggleHomeAssistant() {
this.homeAssistant = !this.homeAssistant;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
.partial-picker ha-formfield { .partial-picker ha-formfield {
@@ -307,7 +307,7 @@ export class SupervisorSnapshotContent extends LitElement {
display: block; display: block;
margin: 0 -14px -16px; margin: 0 -14px -16px;
} }
.snapshot-types { .backup-types {
display: flex; display: flex;
margin-left: -13px; margin-left: -13px;
} }
@@ -317,23 +317,23 @@ export class SupervisorSnapshotContent extends LitElement {
`; `;
} }
public snapshotDetails(): public backupDetails():
| HassioPartialSnapshotCreateParams | HassioPartialBackupCreateParams
| HassioFullSnapshotCreateParams { | HassioFullBackupCreateParams {
const data: any = {}; const data: any = {};
if (!this.snapshot) { if (!this.backup) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale); data.name = this.backupName || formatDate(new Date(), this.hass.locale);
} }
if (this.snapshotHasPassword) { if (this.backupHasPassword) {
data.password = this.snapshotPassword; data.password = this.backupPassword;
if (!this.snapshot) { if (!this.backup) {
data.confirm_password = this.confirmSnapshotPassword; data.confirm_password = this.confirmBackupPassword;
} }
} }
if (this.snapshotType === "full") { if (this.backupType === "full") {
return data; return data;
} }
@@ -415,7 +415,7 @@ export class SupervisorSnapshotContent extends LitElement {
} }
private _toggleHasPassword(): void { private _toggleHasPassword(): void {
this.snapshotHasPassword = !this.snapshotHasPassword; this.backupHasPassword = !this.backupHasPassword;
} }
private _toggleSection(ev): void { private _toggleSection(ev): void {
@@ -445,6 +445,6 @@ export class SupervisorSnapshotContent extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"supervisor-snapshot-content": SupervisorSnapshotContent; "supervisor-backup-content": SupervisorBackupContent;
} }
} }

View File

@@ -20,10 +20,10 @@ class SupervisorMetric extends LitElement {
<div slot="description" .title=${this.tooltip ?? ""}> <div slot="description" .title=${this.tooltip ?? ""}>
<span class="value"> ${roundedValue} % </span> <span class="value"> ${roundedValue} % </span>
<ha-bar <ha-bar
class="${classMap({ class=${classMap({
"target-warning": roundedValue > 50, "target-warning": roundedValue > 50,
"target-critical": roundedValue > 85, "target-critical": roundedValue > 85,
})}" })}
.value=${this.value} .value=${this.value}
></ha-bar> ></ha-bar>
</div> </div>

View File

@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { compare } from "../../../src/common/string/compare"; import { stringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
@@ -33,7 +33,7 @@ class HassioAddons extends LitElement {
</ha-card> </ha-card>
` `
: this.supervisor.supervisor.addons : this.supervisor.supervisor.addons
.sort((a, b) => compare(a.name, b.name)) .sort((a, b) => stringCompare(a.name, b.name))
.map( .map(
(addon) => html` (addon) => html`
<ha-card .addon=${addon} @click=${this._addonTapped}> <ha-card .addon=${addon} @click=${this._addonTapped}>

View File

@@ -136,7 +136,7 @@ export class HassioUpdate extends LitElement {
</ha-settings-row> </ha-settings-row>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer"> <a href=${releaseNotesUrl} target="_blank" rel="noreferrer">
<mwc-button> <mwc-button>
${this.supervisor.localize("common.release_notes")} ${this.supervisor.localize("common.release_notes")}
</mwc-button> </mwc-button>
@@ -162,7 +162,7 @@ export class HassioUpdate extends LitElement {
supervisor: this.supervisor, supervisor: this.supervisor,
name: "Home Assistant Core", name: "Home Assistant Core",
version: this.supervisor.core.version_latest, version: this.supervisor.core.version_latest,
snapshotParams: { backupParams: {
name: `core_${this.supervisor.core.version}`, name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"], folders: ["homeassistant"],
homeassistant: true, homeassistant: true,
@@ -206,7 +206,7 @@ export class HassioUpdate extends LitElement {
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: item.key, collection: item.key,
}); });
} catch (err) { } catch (err: any) {
// Only show an error if the status code was not expected (user behind proxy) // Only show an error if the status code was not expected (user behind proxy)
// or no status at all(connection terminated) // or no status at all(connection terminated)
if (this.hass.connection.connected && !ignoreSupervisorError(err)) { if (this.hass.connection.connected && !ignoreSupervisorError(err)) {

View File

@@ -6,20 +6,20 @@ import "../../../../src/components/ha-header-bar";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles"; import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-upload-snapshot"; import "../../components/hassio-upload-backup";
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload"; import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload";
@customElement("dialog-hassio-snapshot-upload") @customElement("dialog-hassio-backup-upload")
export class DialogHassioSnapshotUpload export class DialogHassioBackupUpload
extends LitElement extends LitElement
implements HassDialog<HassioSnapshotUploadDialogParams> implements HassDialog<HassioBackupUploadDialogParams>
{ {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioSnapshotUploadDialogParams; @state() private _params?: HassioBackupUploadDialogParams;
public async showDialog( public async showDialog(
params: HassioSnapshotUploadDialogParams params: HassioBackupUploadDialogParams
): Promise<void> { ): Promise<void> {
this._params = params; this._params = params;
await this.updateComplete; await this.updateComplete;
@@ -27,8 +27,8 @@ export class DialogHassioSnapshotUpload
public closeDialog(): void { public closeDialog(): void {
if (this._params && !this._params.onboarding) { if (this._params && !this._params.onboarding) {
if (this._params.reloadSnapshot) { if (this._params.reloadBackup) {
this._params.reloadSnapshot(); this._params.reloadBackup();
} }
} }
this._params = undefined; this._params = undefined;
@@ -51,23 +51,23 @@ export class DialogHassioSnapshotUpload
> >
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title"> Upload snapshot </span> <span slot="title"> Upload backup </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel"> <mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
</div> </div>
<hassio-upload-snapshot <hassio-upload-backup
@snapshot-uploaded=${this._snapshotUploaded} @backup-uploaded=${this._backupUploaded}
.hass=${this.hass} .hass=${this.hass}
></hassio-upload-snapshot> ></hassio-upload-backup>
</ha-dialog> </ha-dialog>
`; `;
} }
private _snapshotUploaded(ev) { private _backupUploaded(ev) {
const snapshot = ev.detail.snapshot; const backup = ev.detail.backup;
this._params?.showSnapshot(snapshot.slug); this._params?.showBackup(backup.slug);
this.closeDialog(); this.closeDialog();
} }
@@ -94,6 +94,6 @@ export class DialogHassioSnapshotUpload
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload; "dialog-hassio-backup-upload": DialogHassioBackupUpload;
} }
} }

View File

@@ -6,15 +6,16 @@ import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify"; import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button-menu"; import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth"; import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
fetchHassioSnapshotInfo, fetchHassioBackupInfo,
HassioSnapshotDetail, HassioBackupDetail,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/backup";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@@ -23,44 +24,46 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download"; import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-snapshot-content"; import "../../components/supervisor-backup-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content"; import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot"; import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import { atLeastVersion } from "../../../../src/common/config/version";
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
@customElement("dialog-hassio-snapshot") @customElement("dialog-hassio-backup")
class HassioSnapshotDialog class HassioBackupDialog
extends LitElement extends LitElement
implements HassDialog<HassioSnapshotDialogParams> implements HassDialog<HassioBackupDialogParams>
{ {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string; @state() private _error?: string;
@state() private _snapshot?: HassioSnapshotDetail; @state() private _backup?: HassioBackupDetail;
@state() private _dialogParams?: HassioSnapshotDialogParams; @state() private _dialogParams?: HassioBackupDialogParams;
@state() private _restoringSnapshot = false; @state() private _restoringBackup = false;
@query("supervisor-snapshot-content") @query("supervisor-backup-content")
private _snapshotContent!: SupervisorSnapshotContent; private _backupContent!: SupervisorBackupContent;
public async showDialog(params: HassioSnapshotDialogParams) { public async showDialog(params: HassioBackupDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
this._dialogParams = params; this._dialogParams = params;
this._restoringSnapshot = false; this._restoringBackup = false;
} }
public closeDialog() { public closeDialog() {
this._snapshot = undefined; this._backup = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
this._restoringSnapshot = false; this._restoringBackup = false;
this._error = undefined; this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._dialogParams || !this._snapshot) { if (!this._dialogParams || !this._backup) {
return html``; return html``;
} }
return html` return html`
@@ -72,26 +75,28 @@ class HassioSnapshotDialog
> >
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title">${this._snapshot.name}</span> <span slot="title">${this._backup.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel"> <mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
</div> </div>
${this._restoringSnapshot ${this._restoringBackup
? html` <ha-circular-progress active></ha-circular-progress>` ? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content : html`<supervisor-backup-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
.snapshot=${this._snapshot} .backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false} .onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize} .localize=${this._dialogParams.localize}
> >
</supervisor-snapshot-content>`} </supervisor-backup-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<mwc-button <mwc-button
.disabled=${this._restoringSnapshot} .disabled=${this._restoringBackup}
slot="secondaryAction" slot="secondaryAction"
@click=${this._restoreClicked} @click=${this._restoreClicked}
> >
@@ -103,13 +108,13 @@ class HassioSnapshotDialog
fixed fixed
slot="primaryAction" slot="primaryAction"
@action=${this._handleMenuAction} @action=${this._handleMenuAction}
@closed=${(ev: Event) => ev.stopPropagation()} @closed=${stopPropagation}
> >
<mwc-icon-button slot="trigger" alt="menu"> <mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item>Download Snapshot</mwc-list-item> <mwc-list-item>Download Backup</mwc-list-item>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item> <mwc-list-item class="error">Delete Backup</mwc-list-item>
</ha-button-menu>` </ha-button-menu>`
: ""} : ""}
</ha-dialog> </ha-dialog>
@@ -150,30 +155,30 @@ class HassioSnapshotDialog
} }
private async _restoreClicked() { private async _restoreClicked() {
const snapshotDetails = this._snapshotContent.snapshotDetails(); const backupDetails = this._backupContent.backupDetails();
this._restoringSnapshot = true; this._restoringBackup = true;
if (this._snapshotContent.snapshotType === "full") { if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(snapshotDetails); await this._fullRestoreClicked(backupDetails);
} else { } else {
await this._partialRestoreClicked(snapshotDetails); await this._partialRestoreClicked(backupDetails);
} }
this._restoringSnapshot = false; this._restoringBackup = false;
} }
private async _partialRestoreClicked(snapshotDetails) { private async _partialRestoreClicked(backupDetails) {
if ( if (
this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this._dialogParams?.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore snapshot", title: "Could not restore backup",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?", title: "Are you sure you want partially to restore this backup?",
confirmText: "restore", confirmText: "restore",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -186,8 +191,12 @@ class HassioSnapshotDialog
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`, `hassio/${
snapshotDetails atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
) )
.then( .then(
() => { () => {
@@ -199,29 +208,29 @@ class HassioSnapshotDialog
); );
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, { fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST", method: "POST",
body: JSON.stringify(snapshotDetails), body: JSON.stringify(backupDetails),
}); });
this.closeDialog(); this.closeDialog();
} }
} }
private async _fullRestoreClicked(snapshotDetails) { private async _fullRestoreClicked(backupDetails) {
if ( if (
this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this._dialogParams?.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore snapshot", title: "Could not restore backup",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: title:
"Are you sure you want to wipe your system and restore this snapshot?", "Are you sure you want to wipe your system and restore this backup?",
confirmText: "restore", confirmText: "restore",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -233,8 +242,12 @@ class HassioSnapshotDialog
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`, `hassio/${
snapshotDetails atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
) )
.then( .then(
() => { () => {
@@ -246,9 +259,9 @@ class HassioSnapshotDialog
); );
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, { fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
method: "POST", method: "POST",
body: JSON.stringify(snapshotDetails), body: JSON.stringify(backupDetails),
}); });
this.closeDialog(); this.closeDialog();
} }
@@ -257,7 +270,7 @@ class HassioSnapshotDialog
private async _deleteClicked() { private async _deleteClicked() {
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this snapshot?", title: "Are you sure you want to delete this backup?",
confirmText: "delete", confirmText: "delete",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -267,7 +280,14 @@ class HassioSnapshotDialog
this.hass this.hass
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`) .callApi(
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? `backups/${this._backup!.slug}`
: `snapshots/${this._backup!.slug}/remove`
}`
)
.then( .then(
() => { () => {
if (this._dialogParams!.onDelete) { if (this._dialogParams!.onDelete) {
@@ -286,9 +306,13 @@ class HassioSnapshotDialog
try { try {
signedPath = await getSignedPath( signedPath = await getSignedPath(
this.hass, this.hass,
`/api/hassio/snapshots/${this._snapshot!.slug}/download` `/api/hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/download`
); );
} catch (err) { } catch (err: any) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
@@ -298,7 +322,7 @@ class HassioSnapshotDialog
if (window.location.href.includes("ui.nabu.casa")) { if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, { const confirm = await showConfirmationDialog(this, {
title: "Potential slow download", title: "Potential slow download",
text: "Downloading snapshots over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?", text: "Downloading backups over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
confirmText: "continue", confirmText: "continue",
dismissText: "cancel", dismissText: "cancel",
}); });
@@ -310,19 +334,19 @@ class HassioSnapshotDialog
fileDownload( fileDownload(
this, this,
signedPath.path, signedPath.path,
`home_assistant_snapshot_${slugify(this._computeName)}.tar` `home_assistant_backup_${slugify(this._computeName)}.tar`
); );
} }
private get _computeName() { private get _computeName() {
return this._snapshot return this._backup
? this._snapshot.name || this._snapshot.slug ? this._backup.name || this._backup.slug
: "Unnamed snapshot"; : "Unnamed backup";
} }
} }
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-hassio-snapshot": HassioSnapshotDialog; "dialog-hassio-backup": HassioBackupDialog;
} }
} }

View File

@@ -2,41 +2,42 @@ import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
createHassioFullSnapshot, createHassioFullBackup,
createHassioPartialSnapshot, createHassioPartialBackup,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/backup";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-snapshot-content"; import "../../components/supervisor-backup-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content"; import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot"; import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
@customElement("dialog-hassio-create-snapshot") @customElement("dialog-hassio-create-backup")
class HassioCreateSnapshotDialog extends LitElement { class HassioCreateBackupDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams; @state() private _dialogParams?: HassioCreateBackupDialogParams;
@state() private _error?: string; @state() private _error?: string;
@state() private _creatingSnapshot = false; @state() private _creatingBackup = false;
@query("supervisor-snapshot-content") @query("supervisor-backup-content")
private _snapshotContent!: SupervisorSnapshotContent; private _backupContent!: SupervisorBackupContent;
public showDialog(params: HassioCreateSnapshotDialogParams) { public showDialog(params: HassioCreateBackupDialogParams) {
this._dialogParams = params; this._dialogParams = params;
this._creatingSnapshot = false; this._creatingBackup = false;
} }
public closeDialog() { public closeDialog() {
this._dialogParams = undefined; this._dialogParams = undefined;
this._creatingSnapshot = false; this._creatingBackup = false;
this._error = undefined; this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
@@ -52,82 +53,84 @@ class HassioCreateSnapshotDialog extends LitElement {
@closed=${this.closeDialog} @closed=${this.closeDialog}
.heading=${createCloseHeading( .heading=${createCloseHeading(
this.hass, this.hass,
this._dialogParams.supervisor.localize("snapshot.create_snapshot") this._dialogParams.supervisor.localize("backup.create_backup")
)} )}
> >
${this._creatingSnapshot ${this._creatingBackup
? html` <ha-circular-progress active></ha-circular-progress>` ? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content : html`<supervisor-backup-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
> >
</supervisor-snapshot-content>`} </supervisor-backup-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}> <mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")} ${this._dialogParams.supervisor.localize("common.close")}
</mwc-button> </mwc-button>
<mwc-button <mwc-button
.disabled=${this._creatingSnapshot} .disabled=${this._creatingBackup}
slot="primaryAction" slot="primaryAction"
@click=${this._createSnapshot} @click=${this._createBackup}
> >
${this._dialogParams.supervisor.localize("snapshot.create")} ${this._dialogParams.supervisor.localize("backup.create")}
</mwc-button> </mwc-button>
</ha-dialog> </ha-dialog>
`; `;
} }
private async _createSnapshot(): Promise<void> { private async _createBackup(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") { if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, { showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize( title: this._dialogParams!.supervisor.localize(
"snapshot.could_not_create" "backup.could_not_create"
), ),
text: this._dialogParams!.supervisor.localize( text: this._dialogParams!.supervisor.localize(
"snapshot.create_blocked_not_running", "backup.create_blocked_not_running",
"state", "state",
this._dialogParams!.supervisor.info.state this._dialogParams!.supervisor.info.state
), ),
}); });
return; return;
} }
const snapshotDetails = this._snapshotContent.snapshotDetails(); const backupDetails = this._backupContent.backupDetails();
this._creatingSnapshot = true; this._creatingBackup = true;
this._error = ""; this._error = "";
if (snapshotDetails.password && !snapshotDetails.password.length) { if (backupDetails.password && !backupDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize( this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password" "backup.enter_password"
); );
this._creatingSnapshot = false; this._creatingBackup = false;
return; return;
} }
if ( if (
snapshotDetails.password && backupDetails.password &&
snapshotDetails.password !== snapshotDetails.confirm_password backupDetails.password !== backupDetails.confirm_password
) { ) {
this._error = this._dialogParams!.supervisor.localize( this._error = this._dialogParams!.supervisor.localize(
"snapshot.passwords_not_matching" "backup.passwords_not_matching"
); );
this._creatingSnapshot = false; this._creatingBackup = false;
return; return;
} }
delete snapshotDetails.confirm_password; delete backupDetails.confirm_password;
try { try {
if (this._snapshotContent.snapshotType === "full") { if (this._backupContent.backupType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails); await createHassioFullBackup(this.hass, backupDetails);
} else { } else {
await createHassioPartialSnapshot(this.hass, snapshotDetails); await createHassioPartialBackup(this.hass, backupDetails);
} }
this._dialogParams!.onCreate(); this._dialogParams!.onCreate();
this.closeDialog(); this.closeDialog();
} catch (err) { } catch (err: any) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
this._creatingSnapshot = false; this._creatingBackup = false;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@@ -146,6 +149,6 @@ class HassioCreateSnapshotDialog extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog; "dialog-hassio-create-backup": HassioCreateBackupDialog;
} }
} }

View File

@@ -0,0 +1,19 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-backup-upload";
export interface HassioBackupUploadDialogParams {
showBackup: (slug: string) => void;
reloadBackup?: () => Promise<void>;
onboarding?: boolean;
}
export const showBackupUploadDialog = (
element: HTMLElement,
dialogParams: HassioBackupUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup-upload",
dialogImport: () => import("./dialog-hassio-backup-upload"),
dialogParams,
});
};

View File

@@ -2,7 +2,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize"; import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioSnapshotDialogParams { export interface HassioBackupDialogParams {
slug: string; slug: string;
onDelete?: () => void; onDelete?: () => void;
onboarding?: boolean; onboarding?: boolean;
@@ -10,13 +10,13 @@ export interface HassioSnapshotDialogParams {
localize?: LocalizeFunc; localize?: LocalizeFunc;
} }
export const showHassioSnapshotDialog = ( export const showHassioBackupDialog = (
element: HTMLElement, element: HTMLElement,
dialogParams: HassioSnapshotDialogParams dialogParams: HassioBackupDialogParams
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot", dialogTag: "dialog-hassio-backup",
dialogImport: () => import("./dialog-hassio-snapshot"), dialogImport: () => import("./dialog-hassio-backup"),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -1,18 +1,18 @@
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioCreateSnapshotDialogParams { export interface HassioCreateBackupDialogParams {
supervisor: Supervisor; supervisor: Supervisor;
onCreate: () => void; onCreate: () => void;
} }
export const showHassioCreateSnapshotDialog = ( export const showHassioCreateBackupDialog = (
element: HTMLElement, element: HTMLElement,
dialogParams: HassioCreateSnapshotDialogParams dialogParams: HassioCreateBackupDialogParams
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-create-snapshot", dialogTag: "dialog-hassio-create-backup",
dialogImport: () => import("./dialog-hassio-create-snapshot"), dialogImport: () => import("./dialog-hassio-create-backup"),
dialogParams, dialogParams,
}); });
}; };

View File

@@ -0,0 +1,193 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import {
DatadiskList,
listDatadisks,
moveDatadisk,
} from "../../../../src/data/hassio/host";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioDatatiskDialogParams } from "./show-dialog-hassio-datadisk";
const calculateMoveTime = memoizeOne((supervisor: Supervisor): number => {
const speed = supervisor.host.disk_life_time !== "" ? 30 : 10;
const moveTime = (supervisor.host.disk_used * 1000) / 60 / speed;
const rebootTime = (supervisor.host.startup_time * 4) / 60;
return Math.ceil((moveTime + rebootTime) / 10) * 10;
});
@customElement("dialog-hassio-datadisk")
class HassioDatadiskDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private dialogParams?: HassioDatatiskDialogParams;
@state() private selectedDevice?: string;
@state() private devices?: DatadiskList["devices"];
@state() private moving = false;
public showDialog(params: HassioDatatiskDialogParams) {
this.dialogParams = params;
listDatadisks(this.hass).then((data) => {
this.devices = data.devices;
});
}
public closeDialog(): void {
this.dialogParams = undefined;
this.selectedDevice = undefined;
this.devices = undefined;
this.moving = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this.dialogParams) {
return html``;
}
let heading: string;
let content: TemplateResult;
if (this.moving) {
heading = this.dialogParams.supervisor.localize(
"dialog.datadisk_move.moving"
);
content = html`
<ha-circular-progress alt="Moving" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.moving_desc"
)}
</p>
`;
} else {
heading = this.dialogParams.supervisor.localize(
"dialog.datadisk_move.title"
);
content = html`
${this.devices?.length
? html`
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.description",
{
current_path: this.dialogParams.supervisor.os.data_disk,
time: calculateMoveTime(this.dialogParams.supervisor),
}
)}
<br /><br />
<paper-dropdown-menu
.label=${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.select_device"
)}
@value-changed=${this._select_device}
>
<paper-listbox slot="dropdown-content">
${this.devices.map(
(device) => html`<paper-item>${device}</paper-item>`
)}
</paper-listbox>
</paper-dropdown-menu>
`
: this.devices === undefined
? this.dialogParams.supervisor.localize(
"dialog.datadisk_move.loading_devices"
)
: this.dialogParams.supervisor.localize(
"dialog.datadisk_move.no_devices"
)}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.cancel"
)}
</mwc-button>
<mwc-button
.disabled=${!this.selectedDevice}
slot="primaryAction"
@click=${this._moveDatadisk}
>
${this.dialogParams.supervisor.localize("dialog.datadisk_move.move")}
</mwc-button>
`;
}
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
@closed=${this.closeDialog}
?hideActions=${this.moving}
.heading=${heading}
>
${content}
</ha-dialog>
`;
}
private _select_device(event) {
this.selectedDevice = event.detail.value;
}
private async _moveDatadisk() {
this.moving = true;
try {
await moveDatadisk(this.hass, this.selectedDevice!);
} catch (err: any) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.dialogParams!.supervisor.localize(
"system.host.failed_to_move"
),
text: extractApiErrorMessage(err),
});
this.closeDialog();
}
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
paper-dropdown-menu {
width: 100%;
}
ha-circular-progress {
display: block;
margin: 32px;
text-align: center;
}
.progress-text {
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-datadisk": HassioDatadiskDialog;
}
}

View File

@@ -0,0 +1,17 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioDatatiskDialogParams {
supervisor: Supervisor;
}
export const showHassioDatadiskDialog = (
element: HTMLElement,
dialogParams: HassioDatatiskDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-datadisk",
dialogImport: () => import("./dialog-hassio-datadisk"),
dialogParams,
});
};

View File

@@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/common/search/search-input"; import "../../../../src/common/search/search-input";
import { compare } from "../../../../src/common/string/compare"; import { stringCompare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel"; import "../../../../src/components/ha-expansion-panel";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware"; import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
@@ -27,7 +27,7 @@ const _filterDevices = memoizeOne(
.toLocaleLowerCase() .toLocaleLowerCase()
.includes(filter)) .includes(filter))
) )
.sort((a, b) => compare(a.name, b.name)) .sort((a, b) => stringCompare(a.name, b.name))
); );
@customElement("dialog-hassio-hardware") @customElement("dialog-hassio-hardware")

View File

@@ -10,6 +10,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache"; import { cache } from "lit/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel"; import "../../../../src/components/ha-expansion-panel";
@@ -251,9 +252,9 @@ export class DialogHassioNetwork
` `
: ""} : ""}
${this._dirty ${this._dirty
? html`<div class="warning"> ? html`<ha-alert alert-type="warning">
${this.supervisor.localize("dialog.network.warning")} ${this.supervisor.localize("dialog.network.warning")}
</div>` </ha-alert>`
: ""} : ""}
</div> </div>
<div class="buttons"> <div class="buttons">
@@ -286,7 +287,7 @@ export class DialogHassioNetwork
this.hass, this.hass,
this._interface.interface this._interface.interface
); );
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to scan for accesspoints", title: "Failed to scan for accesspoints",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -447,7 +448,7 @@ export class DialogHassioNetwork
this._interface!.interface, this._interface!.interface,
interfaceOptions interfaceOptions
); );
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("dialog.network.failed_to_change"), title: this.supervisor.localize("dialog.network.failed_to_change"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),

View File

@@ -190,7 +190,7 @@ class HassioRegistriesDialog extends LitElement {
await addHassioDockerRegistry(this.hass, data); await addHassioDockerRegistry(this.hass, data);
await this._loadRegistries(); await this._loadRegistries();
this._addingRegistry = false; this._addingRegistry = false;
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("dialog.registries.failed_to_add"), title: this.supervisor.localize("dialog.registries.failed_to_add"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -204,7 +204,7 @@ class HassioRegistriesDialog extends LitElement {
try { try {
await removeHassioDockerRegistry(this.hass, entry.registry); await removeHassioDockerRegistry(this.hass, entry.registry);
await this._loadRegistries(); await this._loadRegistries();
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("dialog.registries.failed_to_remove"), title: this.supervisor.localize("dialog.registries.failed_to_remove"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),

View File

@@ -9,6 +9,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
@@ -75,7 +76,9 @@ class HassioRepositoriesDialog extends LitElement {
this._dialogParams!.supervisor.localize("dialog.repositories.title") this._dialogParams!.supervisor.localize("dialog.repositories.title")
)} )}
> >
${this._error ? html`<div class="error">${this._error}</div>` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<div class="form"> <div class="form">
${repositories.length ${repositories.length
? repositories.map( ? repositories.map(
@@ -182,7 +185,7 @@ class HassioRepositoriesDialog extends LitElement {
this._repositories = addonsinfo.repositories; this._repositories = addonsinfo.repositories;
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err) { } catch (err: any) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
} }
@@ -204,7 +207,7 @@ class HassioRepositoriesDialog extends LitElement {
await this._loadData(); await this._loadData();
input.value = ""; input.value = "";
} catch (err) { } catch (err: any) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
this._processing = false; this._processing = false;
@@ -226,7 +229,7 @@ class HassioRepositoriesDialog extends LitElement {
addons_repositories: newRepositories, addons_repositories: newRepositories,
}); });
await this._loadData(); await this._loadData();
} catch (err) { } catch (err: any) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
} }

View File

@@ -1,19 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-snapshot-upload";
export interface HassioSnapshotUploadDialogParams {
showSnapshot: (slug: string) => void;
reloadSnapshot?: () => Promise<void>;
onboarding?: boolean;
}
export const showSnapshotUploadDialog = (
element: HTMLElement,
dialogParams: HassioSnapshotUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot-upload",
dialogImport: () => import("./dialog-hassio-snapshot-upload"),
dialogParams,
});
};

View File

@@ -26,7 +26,7 @@ export const suggestAddonRestart = async (
if (confirmed) { if (confirmed) {
try { try {
await restartHassioAddon(hass, addon.slug); await restartHassioAddon(hass, addon.slug);
} catch (err) { } catch (err: any) {
showAlertDialog(element, { showAlertDialog(element, {
title: supervisor.localize( title: supervisor.localize(
"common.failed_to_restart_name", "common.failed_to_restart_name",

View File

@@ -2,6 +2,7 @@ import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-settings-row";
@@ -11,7 +12,7 @@ import {
extractApiErrorMessage, extractApiErrorMessage,
ignoreSupervisorError, ignoreSupervisorError,
} from "../../../../src/data/hassio/common"; } from "../../../../src/data/hassio/common";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot"; import { createHassioPartialBackup } from "../../../../src/data/hassio/backup";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update"; import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
@@ -22,9 +23,9 @@ class DialogSupervisorUpdate extends LitElement {
@state() private _opened = false; @state() private _opened = false;
@state() private _createSnapshot = true; @state() private _createBackup = true;
@state() private _action: "snapshot" | "update" | null = null; @state() private _action: "backup" | "update" | null = null;
@state() private _error?: string; @state() private _error?: string;
@@ -41,7 +42,7 @@ class DialogSupervisorUpdate extends LitElement {
public closeDialog(): void { public closeDialog(): void {
this._action = null; this._action = null;
this._createSnapshot = true; this._createBackup = true;
this._error = undefined; this._error = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
@@ -84,20 +85,20 @@ class DialogSupervisorUpdate extends LitElement {
<ha-settings-row> <ha-settings-row>
<span slot="heading"> <span slot="heading">
${this._dialogParams.supervisor.localize( ${this._dialogParams.supervisor.localize(
"dialog.update.snapshot" "dialog.update.backup"
)} )}
</span> </span>
<span slot="description"> <span slot="description">
${this._dialogParams.supervisor.localize( ${this._dialogParams.supervisor.localize(
"dialog.update.create_snapshot", "dialog.update.create_backup",
"name", "name",
this._dialogParams.name this._dialogParams.name
)} )}
</span> </span>
<ha-switch <ha-switch
.checked=${this._createSnapshot} .checked=${this._createBackup}
haptic haptic
@click=${this._toggleSnapshot} @click=${this._toggleBackup}
> >
</ha-switch> </ha-switch>
</ha-settings-row> </ha-settings-row>
@@ -123,29 +124,31 @@ class DialogSupervisorUpdate extends LitElement {
this._dialogParams.version this._dialogParams.version
) )
: this._dialogParams.supervisor.localize( : this._dialogParams.supervisor.localize(
"dialog.update.snapshotting", "dialog.update.creating_backup",
"name", "name",
this._dialogParams.name this._dialogParams.name
)} )}
</p>`} </p>`}
${this._error ? html`<p class="error">${this._error}</p>` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
</ha-dialog> </ha-dialog>
`; `;
} }
private _toggleSnapshot() { private _toggleBackup() {
this._createSnapshot = !this._createSnapshot; this._createBackup = !this._createBackup;
} }
private async _update() { private async _update() {
if (this._createSnapshot) { if (this._createBackup) {
this._action = "snapshot"; this._action = "backup";
try { try {
await createHassioPartialSnapshot( await createHassioPartialBackup(
this.hass, this.hass,
this._dialogParams!.snapshotParams this._dialogParams!.backupParams
); );
} catch (err) { } catch (err: any) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
this._action = null; this._action = null;
return; return;
@@ -155,7 +158,7 @@ class DialogSupervisorUpdate extends LitElement {
this._action = "update"; this._action = "update";
try { try {
await this._dialogParams!.updateHandler!(); await this._dialogParams!.updateHandler!();
} catch (err) { } catch (err: any) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) { if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
this._action = null; this._action = null;

View File

@@ -5,7 +5,7 @@ export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor; supervisor: Supervisor;
name: string; name: string;
version: string; version: string;
snapshotParams: any; backupParams: any;
updateHandler: () => Promise<void>; updateHandler: () => Promise<void>;
} }

View File

@@ -26,7 +26,10 @@ const REDIRECTS: Redirects = {
redirect: "/hassio/system", redirect: "/hassio/system",
}, },
supervisor_snapshots: { supervisor_snapshots: {
redirect: "/hassio/snapshots", redirect: "/hassio/backups",
},
supervisor_backups: {
redirect: "/hassio/backups",
}, },
supervisor_store: { supervisor_store: {
redirect: "/hassio/store", redirect: "/hassio/store",
@@ -84,7 +87,7 @@ class HassioMyRedirect extends LitElement {
let url: string; let url: string;
try { try {
url = this._createRedirectUrl(redirect); url = this._createRedirectUrl(redirect);
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize("my.error"); this._error = this.supervisor.localize("my.error");
return; return;
} }

View File

@@ -9,7 +9,7 @@ import "./addon-store/hassio-addon-store";
// Don't codesplit it, that way the dashboard always loads fast. // Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard"; import "./dashboard/hassio-dashboard";
// Don't codesplit the others, because it breaks the UI when pushed to a Pi // Don't codesplit the others, because it breaks the UI when pushed to a Pi
import "./snapshots/hassio-snapshots"; import "./backups/hassio-backups";
import "./system/hassio-system"; import "./system/hassio-system";
@customElement("hassio-panel-router") @customElement("hassio-panel-router")
@@ -23,6 +23,8 @@ class HassioPanelRouter extends HassRouterPage {
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
routes: { routes: {
dashboard: { dashboard: {
tag: "hassio-dashboard", tag: "hassio-dashboard",
@@ -30,8 +32,8 @@ class HassioPanelRouter extends HassRouterPage {
store: { store: {
tag: "hassio-addon-store", tag: "hassio-addon-store",
}, },
snapshots: { backups: {
tag: "hassio-snapshots", tag: "hassio-backups",
}, },
system: { system: {
tag: "hassio-system", tag: "hassio-system",

View File

@@ -23,6 +23,8 @@ class HassioRouter extends HassRouterPage {
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it. // Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard", defaultPage: "dashboard",
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
initialLoad: () => this._redirectIngress(), initialLoad: () => this._redirectIngress(),
showLoading: true, showLoading: true,
routes: { routes: {
@@ -30,7 +32,7 @@ class HassioRouter extends HassRouterPage {
tag: "hassio-panel", tag: "hassio-panel",
cache: true, cache: true,
}, },
snapshots: "dashboard", backups: "dashboard",
store: "dashboard", store: "dashboard",
system: "dashboard", system: "dashboard",
addon: { addon: {

View File

@@ -13,8 +13,8 @@ export const supervisorTabs: PageNavigation[] = [
iconPath: mdiStore, iconPath: mdiStore,
}, },
{ {
translationKey: "panel.snapshots", translationKey: "panel.backups",
path: `/hassio/snapshots`, path: `/hassio/backups`,
iconPath: mdiBackupRestore, iconPath: mdiBackupRestore,
}, },
{ {

View File

@@ -91,7 +91,7 @@ class HassioIngressView extends LitElement {
if (requestedAddon) { if (requestedAddon) {
try { try {
addonInfo = await fetchHassioAddonInfo(this.hass, requestedAddon); addonInfo = await fetchHassioAddonInfo(this.hass, requestedAddon);
} catch (err) { } catch (err: any) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
title: requestedAddon, title: requestedAddon,
@@ -145,7 +145,7 @@ class HassioIngressView extends LitElement {
try { try {
addon = await fetchHassioAddonInfo(this.hass, addonSlug); addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (err) { } catch (err: any) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: "Unable to fetch add-on info to start Ingress", text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor", title: "Supervisor",
@@ -179,7 +179,7 @@ class HassioIngressView extends LitElement {
try { try {
session = await createSessionPromise; session = await createSessionPromise;
} catch (err) { } catch (err: any) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: "Unable to create an Ingress session", text: "Unable to create an Ingress session",
title: addon.name, title: addon.name,
@@ -195,7 +195,7 @@ class HassioIngressView extends LitElement {
this._sessionKeepAlive = window.setInterval(async () => { this._sessionKeepAlive = window.setInterval(async () => {
try { try {
await validateHassioSession(this.hass, session); await validateHassioSession(this.hass, session);
} catch (err) { } catch (err: any) {
session = await createHassioSession(this.hass); session = await createHassioSession(this.hass);
} }
}, 60000); }, 60000);

View File

@@ -144,7 +144,7 @@ class HassioCoreInfo extends LitElement {
try { try {
await restartCore(this.hass); await restartCore(this.hass);
} catch (err) { } catch (err: any) {
if (this.hass.connection.connected) { if (this.hass.connection.connected) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
@@ -165,7 +165,7 @@ class HassioCoreInfo extends LitElement {
supervisor: this.supervisor, supervisor: this.supervisor,
name: "Home Assistant Core", name: "Home Assistant Core",
version: this.supervisor.core.version_latest, version: this.supervisor.core.version_latest,
snapshotParams: { backupParams: {
name: `core_${this.supervisor.core.version}`, name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"], folders: ["homeassistant"],
homeassistant: true, homeassistant: true,

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
@@ -40,8 +39,9 @@ import {
roundWithOneDecimal, roundWithOneDecimal,
} from "../../../src/util/calculate"; } from "../../../src/util/calculate";
import "../components/supervisor-metric"; import "../components/supervisor-metric";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network"; import { showHassioDatadiskDialog } from "../dialogs/datadisk/show-dialog-hassio-datadisk";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware"; import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-host-info") @customElement("hassio-host-info")
@@ -180,20 +180,38 @@ class HassioHostInfo extends LitElement {
` `
: ""} : ""}
<ha-button-menu <ha-button-menu corner="BOTTOM_START">
corner="BOTTOM_START"
@action=${this._handleMenuAction}
>
<mwc-icon-button slot="trigger"> <mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item> <mwc-list-item
.action=${"hardware"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize("system.host.hardware")} ${this.supervisor.localize("system.host.hardware")}
</mwc-list-item> </mwc-list-item>
${this.supervisor.host.features.includes("haos") ${this.supervisor.host.features.includes("haos")
? html`<mwc-list-item> ? html`
${this.supervisor.localize("system.host.import_from_usb")} <mwc-list-item
</mwc-list-item>` .action=${"import_from_usb"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>
${this.supervisor.host.features.includes("os_agent") &&
atLeastVersion(this.supervisor.host.agent_version, 1, 2, 0)
? html`
<mwc-list-item
.action=${"move_datadisk"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize(
"system.host.move_datadisk"
)}
</mwc-list-item>
`
: ""}
`
: ""} : ""}
</ha-button-menu> </ha-button-menu>
</div> </div>
@@ -216,22 +234,31 @@ class HassioHostInfo extends LitElement {
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0]; return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
}); });
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) { private async _handleMenuAction(ev) {
switch (ev.detail.index) { switch ((ev.target as any).action) {
case 0: case "hardware":
await this._showHardware(); await this._showHardware();
break; break;
case 1: case "import_from_usb":
await this._importFromUSB(); await this._importFromUSB();
break; break;
case "move_datadisk":
await this._moveDatadisk();
break;
} }
} }
private _moveDatadisk(): void {
showHassioDatadiskDialog(this, {
supervisor: this.supervisor,
});
}
private async _showHardware(): Promise<void> { private async _showHardware(): Promise<void> {
let hardware; let hardware;
try { try {
hardware = await fetchHassioHardwareInfo(this.hass); hardware = await fetchHassioHardwareInfo(this.hass);
} catch (err) { } catch (err: any) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.host.failed_to_get_hardware_list" "system.host.failed_to_get_hardware_list"
@@ -261,7 +288,7 @@ class HassioHostInfo extends LitElement {
try { try {
await rebootHost(this.hass); await rebootHost(this.hass);
} catch (err) { } catch (err: any) {
// Ignore connection errors, these are all expected // Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) { if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, { showAlertDialog(this, {
@@ -291,7 +318,7 @@ class HassioHostInfo extends LitElement {
try { try {
await shutdownHost(this.hass); await shutdownHost(this.hass);
} catch (err) { } catch (err: any) {
// Ignore connection errors, these are all expected // Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) { if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, { showAlertDialog(this, {
@@ -332,7 +359,7 @@ class HassioHostInfo extends LitElement {
try { try {
await updateOS(this.hass); await updateOS(this.hass);
fireEvent(this, "supervisor-collection-refresh", { collection: "os" }); fireEvent(this, "supervisor-collection-refresh", { collection: "os" });
} catch (err) { } catch (err: any) {
if (this.hass.connection.connected) { if (this.hass.connection.connected) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
@@ -370,7 +397,7 @@ class HassioHostInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "host", collection: "host",
}); });
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_set_hostname"), title: this.supervisor.localize("system.host.failed_to_set_hostname"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -385,7 +412,7 @@ class HassioHostInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "host", collection: "host",
}); });
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.host.failed_to_import_from_usb" "system.host.failed_to_import_from_usb"

View File

@@ -3,6 +3,7 @@ import { customElement, property, state } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
@@ -33,16 +34,18 @@ import { hassioStyle } from "../resources/hassio-style";
const UNSUPPORTED_REASON_URL = { const UNSUPPORTED_REASON_URL = {
apparmor: "/more-info/unsupported/apparmor", apparmor: "/more-info/unsupported/apparmor",
container: "/more-info/unsupported/container", container: "/more-info/unsupported/container",
content_trust: "/more-info/unsupported/content_trust",
dbus: "/more-info/unsupported/dbus", dbus: "/more-info/unsupported/dbus",
docker_configuration: "/more-info/unsupported/docker_configuration", docker_configuration: "/more-info/unsupported/docker_configuration",
docker_version: "/more-info/unsupported/docker_version", docker_version: "/more-info/unsupported/docker_version",
job_conditions: "/more-info/unsupported/job_conditions", job_conditions: "/more-info/unsupported/job_conditions",
lxc: "/more-info/unsupported/lxc", lxc: "/more-info/unsupported/lxc",
network_manager: "/more-info/unsupported/network_manager", network_manager: "/more-info/unsupported/network_manager",
os_agent: "/more-info/unsupported/os_agent",
os: "/more-info/unsupported/os", os: "/more-info/unsupported/os",
privileged: "/more-info/unsupported/privileged", privileged: "/more-info/unsupported/privileged",
source_mods: "/more-info/unsupported/source_mods",
systemd: "/more-info/unsupported/systemd", systemd: "/more-info/unsupported/systemd",
content_trust: "/more-info/unsupported/content_trust",
}; };
const UNHEALTHY_REASON_URL = { const UNHEALTHY_REASON_URL = {
@@ -170,31 +173,25 @@ class HassioSupervisorInfo extends LitElement {
></ha-switch> ></ha-switch>
</ha-settings-row>` </ha-settings-row>`
: "" : ""
: html`<div class="error"> : html`<ha-alert
alert-type="warning"
.actionText=${this.supervisor.localize("common.learn_more")}
@alert-action-clicked=${this._unsupportedDialog}
>
${this.supervisor.localize( ${this.supervisor.localize(
"system.supervisor.unsupported_title" "system.supervisor.unsupported_title"
)} )}
<button </ha-alert>`}
class="link"
.title=${this.supervisor.localize("common.learn_more")}
@click=${this._unsupportedDialog}
>
Learn more
</button>
</div>`}
${!this.supervisor.supervisor.healthy ${!this.supervisor.supervisor.healthy
? html`<div class="error"> ? html`<ha-alert
alert-type="error"
.actionText=${this.supervisor.localize("common.learn_more")}
@alert-action-clicked=${this._unhealthyDialog}
>
${this.supervisor.localize( ${this.supervisor.localize(
"system.supervisor.unhealthy_title" "system.supervisor.unhealthy_title"
)} )}
<button </ha-alert>`
class="link"
.title=${this.supervisor.localize("common.learn_more")}
@click=${this._unhealthyDialog}
>
Learn more
</button>
</div>`
: ""} : ""}
</div> </div>
<div class="metrics-block"> <div class="metrics-block">
@@ -285,7 +282,7 @@ class HassioSupervisorInfo extends LitElement {
}; };
await setSupervisorOption(this.hass, data); await setSupervisorOption(this.hass, data);
await this._reloadSupervisor(); await this._reloadSupervisor();
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.supervisor.failed_to_set_option" "system.supervisor.failed_to_set_option"
@@ -303,7 +300,7 @@ class HassioSupervisorInfo extends LitElement {
try { try {
await this._reloadSupervisor(); await this._reloadSupervisor();
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("system.supervisor.failed_to_reload"), title: this.supervisor.localize("system.supervisor.failed_to_reload"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -346,7 +343,7 @@ class HassioSupervisorInfo extends LitElement {
try { try {
await restartSupervisor(this.hass); await restartSupervisor(this.hass);
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"common.failed_to_restart_name", "common.failed_to_restart_name",
@@ -391,7 +388,7 @@ class HassioSupervisorInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor", collection: "supervisor",
}); });
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"common.failed_to_update_name", "common.failed_to_update_name",
@@ -430,10 +427,10 @@ class HassioSupervisorInfo extends LitElement {
<li> <li>
${UNSUPPORTED_REASON_URL[reason] ${UNSUPPORTED_REASON_URL[reason]
? html`<a ? html`<a
href="${documentationUrl( href=${documentationUrl(
this.hass, this.hass,
UNSUPPORTED_REASON_URL[reason] UNSUPPORTED_REASON_URL[reason]
)}" )}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
@@ -461,10 +458,10 @@ class HassioSupervisorInfo extends LitElement {
<li> <li>
${UNHEALTHY_REASON_URL[reason] ${UNHEALTHY_REASON_URL[reason]
? html`<a ? html`<a
href="${documentationUrl( href=${documentationUrl(
this.hass, this.hass,
UNHEALTHY_REASON_URL[reason] UNHEALTHY_REASON_URL[reason]
)}" )}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
@@ -486,7 +483,7 @@ class HassioSupervisorInfo extends LitElement {
diagnostics: !this.supervisor.supervisor?.diagnostics, diagnostics: !this.supervisor.supervisor?.diagnostics,
}; };
await setSupervisorOption(this.hass, data); await setSupervisorOption(this.hass, data);
} catch (err) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.supervisor.failed_to_set_option" "system.supervisor.failed_to_set_option"

View File

@@ -5,6 +5,7 @@ import "@polymer/paper-listbox/paper-listbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor"; import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
@@ -67,7 +68,9 @@ class HassioSupervisorLog extends LitElement {
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<ha-card> <ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this.hass.userData?.showAdvanced ${this.hass.userData?.showAdvanced
? html` ? html`
<paper-dropdown-menu <paper-dropdown-menu
@@ -127,7 +130,7 @@ class HassioSupervisorLog extends LitElement {
this.hass, this.hass,
this._selectedLogProvider this._selectedLogProvider
); );
} catch (err) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"system.log.get_logs", "system.log.get_logs",
"provider", "provider",
@@ -154,10 +157,6 @@ class HassioSupervisorLog extends LitElement {
padding: 0 2%; padding: 0 2%;
width: 96%; width: 96%;
} }
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
`, `,
]; ];
} }

3
netlify.toml Normal file
View File

@@ -0,0 +1,3 @@
[build.environment]
YARN_VERSION = "1.22.11"
NODE_OPTIONS = "--max_old_space_size=6144"

View File

@@ -16,53 +16,56 @@
"lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md", "lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md",
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types", "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
"format": "yarn run format:eslint && yarn run format:prettier", "format": "yarn run format:eslint && yarn run format:prettier",
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"", "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\""
"test": "yarn run lint && yarn run mocha"
}, },
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^5.0.1", "@braintree/sanitize-url": "^5.0.2",
"@codemirror/commands": "^0.18.0", "@codemirror/commands": "^0.19.2",
"@codemirror/gutter": "^0.18.0", "@codemirror/gutter": "^0.19.1",
"@codemirror/highlight": "^0.18.0", "@codemirror/highlight": "^0.19.2",
"@codemirror/history": "^0.18.0", "@codemirror/history": "^0.19.0",
"@codemirror/legacy-modes": "^0.18.0", "@codemirror/legacy-modes": "^0.19.0",
"@codemirror/rectangular-selection": "^0.18.0", "@codemirror/rectangular-selection": "^0.19.0",
"@codemirror/search": "^0.18.0", "@codemirror/search": "^0.19.0",
"@codemirror/state": "^0.18.0", "@codemirror/state": "^0.19.1",
"@codemirror/stream-parser": "^0.18.0", "@codemirror/stream-parser": "^0.19.1",
"@codemirror/text": "^0.18.0", "@codemirror/text": "^0.19.2",
"@codemirror/view": "^0.18.0", "@codemirror/view": "^0.19.4",
"@formatjs/intl-getcanonicallocales": "^1.5.10", "@formatjs/intl-datetimeformat": "^4.2.4",
"@formatjs/intl-locale": "^2.4.28", "@formatjs/intl-getcanonicallocales": "^1.7.3",
"@formatjs/intl-pluralrules": "^4.0.22", "@formatjs/intl-locale": "^2.4.38",
"@fullcalendar/common": "5.1.0", "@formatjs/intl-numberformat": "^7.2.4",
"@fullcalendar/core": "5.1.0", "@formatjs/intl-pluralrules": "^4.1.4",
"@fullcalendar/daygrid": "5.1.0", "@formatjs/intl-relativetimeformat": "^9.3.1",
"@fullcalendar/interaction": "5.1.0", "@formatjs/intl-utils": "^3.8.4",
"@fullcalendar/list": "5.1.0", "@fullcalendar/common": "5.9.0",
"@fullcalendar/core": "5.9.0",
"@fullcalendar/daygrid": "5.9.0",
"@fullcalendar/interaction": "5.9.0",
"@fullcalendar/list": "5.9.0",
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch", "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
"@material/chips": "12.0.0-canary.22d29cbb4.0", "@material/chips": "13.0.0-canary.65125b3a6.0",
"@material/data-table": "12.0.0-canary.22d29cbb4.0", "@material/data-table": "13.0.0-canary.65125b3a6.0",
"@material/mwc-button": "0.22.1", "@material/mwc-button": "0.25.1",
"@material/mwc-checkbox": "0.22.1", "@material/mwc-checkbox": "0.25.1",
"@material/mwc-circular-progress": "0.22.1", "@material/mwc-circular-progress": "0.25.1",
"@material/mwc-dialog": "0.22.1", "@material/mwc-dialog": "0.25.1",
"@material/mwc-fab": "0.22.1", "@material/mwc-fab": "0.25.1",
"@material/mwc-formfield": "0.22.1", "@material/mwc-formfield": "0.25.1",
"@material/mwc-icon-button": "0.22.1", "@material/mwc-icon-button": "0.25.1",
"@material/mwc-linear-progress": "0.22.1", "@material/mwc-linear-progress": "0.25.1",
"@material/mwc-list": "0.22.1", "@material/mwc-list": "0.25.1",
"@material/mwc-menu": "0.22.1", "@material/mwc-menu": "0.25.1",
"@material/mwc-radio": "0.22.1", "@material/mwc-radio": "0.25.1",
"@material/mwc-ripple": "0.22.1", "@material/mwc-ripple": "0.25.1",
"@material/mwc-switch": "0.22.1", "@material/mwc-switch": "0.25.1",
"@material/mwc-tab": "0.22.1", "@material/mwc-tab": "0.25.1",
"@material/mwc-tab-bar": "0.22.1", "@material/mwc-tab-bar": "0.25.1",
"@material/top-app-bar": "12.0.0-canary.22d29cbb4.0", "@material/top-app-bar": "13.0.0-canary.65125b3a6.0",
"@mdi/js": "5.9.55", "@mdi/js": "6.1.95",
"@mdi/svg": "5.9.55", "@mdi/svg": "6.1.95",
"@polymer/app-layout": "^3.1.0", "@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1", "@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1", "@polymer/iron-icon": "^3.0.1",
@@ -88,9 +91,9 @@
"@polymer/paper-toast": "^3.0.1", "@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1", "@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.2", "@thomasloven/round-slider": "0.5.4",
"@vaadin/vaadin-combo-box": "^20.0.1", "@vaadin/vaadin-combo-box": "^21.0.2",
"@vaadin/vaadin-date-picker": "^20.0.1", "@vaadin/vaadin-date-picker": "^21.0.2",
"@vibrant/color": "^3.2.1-alpha.1", "@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1", "@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
@@ -99,36 +102,34 @@
"chart.js": "^3.3.2", "chart.js": "^3.3.2",
"comlink": "^4.3.1", "comlink": "^4.3.1",
"core-js": "^3.15.2", "core-js": "^3.15.2",
"cropperjs": "^1.5.11", "cropperjs": "^1.5.12",
"date-fns": "^2.22.1", "date-fns": "^2.23.0",
"deep-clone-simple": "^1.1.1", "deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1", "deep-freeze": "^0.0.1",
"fecha": "^4.2.0",
"fuse.js": "^6.0.0", "fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2", "google-timezones-json": "^1.0.2",
"hls.js": "^1.0.7", "hls.js": "^1.0.10",
"home-assistant-js-websocket": "^5.11.1", "home-assistant-js-websocket": "^5.11.1",
"idb-keyval": "^5.0.5", "idb-keyval": "^5.1.3",
"intl-messageformat": "^9.6.16", "intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4", "leaflet-draw": "^1.0.4",
"lit": "^2.0.0-rc.2", "lit": "^2.0.0",
"lit-vaadin-helpers": "^0.1.3", "lit-vaadin-helpers": "^0.2.1",
"marked": "^2.0.5", "marked": "^3.0.2",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.2.1", "memoize-one": "^5.2.1",
"node-vibrant": "3.2.1-alpha.1", "node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "^0.3.1", "proxy-polyfill": "^0.3.2",
"punycode": "^2.1.1", "punycode": "^2.1.1",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"regenerator-runtime": "^0.13.8", "regenerator-runtime": "^0.13.8",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"sortablejs": "^1.10.2", "sortablejs": "^1.14.0",
"superstruct": "^0.15.2", "superstruct": "^0.15.2",
"tinykeys": "^1.1.3", "tinykeys": "^1.1.3",
"tsparticles": "^1.19.2", "tsparticles": "^1.34.0",
"unfetch": "^4.1.0", "unfetch": "^4.1.0",
"vis-data": "^7.1.2", "vis-data": "^7.1.2",
"vis-network": "^8.5.4", "vis-network": "^8.5.4",
@@ -144,17 +145,18 @@
"xss": "^1.0.9" "xss": "^1.0.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.6", "@babel/core": "^7.15.5",
"@babel/plugin-external-helpers": "^7.14.5", "@babel/plugin-external-helpers": "^7.14.5",
"@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-decorators": "^7.14.5", "@babel/plugin-proposal-decorators": "^7.15.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.14.7", "@babel/plugin-proposal-object-rest-spread": "^7.15.6",
"@babel/plugin-proposal-optional-chaining": "^7.14.5", "@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/preset-env": "^7.14.7", "@babel/plugin-syntax-top-level-await": "^7.14.5",
"@babel/preset-typescript": "^7.14.5", "@babel/preset-env": "^7.15.6",
"@babel/preset-typescript": "^7.15.0",
"@koa/cors": "^3.1.0", "@koa/cors": "^3.1.0",
"@open-wc/dev-server-hmr": "^0.0.2", "@open-wc/dev-server-hmr": "^0.0.2",
"@rollup/plugin-babel": "^5.2.1", "@rollup/plugin-babel": "^5.2.1",
@@ -164,30 +166,31 @@
"@rollup/plugin-replace": "^2.3.2", "@rollup/plugin-replace": "^2.3.2",
"@types/chromecast-caf-receiver": "5.0.12", "@types/chromecast-caf-receiver": "5.0.12",
"@types/chromecast-caf-sender": "^1.0.3", "@types/chromecast-caf-sender": "^1.0.3",
"@types/js-yaml": "^4.0.1", "@types/js-yaml": "^4",
"@types/leaflet": "^1.7.0", "@types/leaflet": "^1",
"@types/leaflet-draw": "^1.0.3", "@types/leaflet-draw": "^1",
"@types/marked": "^2.0.3", "@types/marked": "^2",
"@types/mocha": "^8.2.2", "@types/mocha": "^8",
"@types/sortablejs": "^1.10.6", "@types/sortablejs": "^1",
"@types/webspeechapi": "^0.0.29", "@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^4.28.3", "@typescript-eslint/eslint-plugin": "^4.32.0",
"@typescript-eslint/parser": "^4.28.3", "@typescript-eslint/parser": "^4.32.0",
"@web/dev-server": "^0.0.24", "@web/dev-server": "^0.0.24",
"@web/dev-server-rollup": "^0.2.11", "@web/dev-server-rollup": "^0.2.11",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"chai": "^4.3.4", "chai": "^4.3.4",
"del": "^4.0.0", "del": "^4.0.0",
"eslint": "^7.30.0", "eslint": "^7.32.0",
"eslint-config-airbnb-typescript": "^12.3.1", "eslint-config-airbnb-base": "^14.2.1",
"eslint-config-airbnb-typescript": "^14.0.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.1", "eslint-import-resolver-webpack": "^0.13.1",
"eslint-plugin-disable": "^2.0.1", "eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.23.4", "eslint-plugin-import": "^2.24.2",
"eslint-plugin-lit": "^1.5.1", "eslint-plugin-lit": "^1.5.1",
"eslint-plugin-prettier": "^3.4.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unused-imports": "^1.1.2", "eslint-plugin-unused-imports": "^1.1.5",
"eslint-plugin-wc": "^1.3.0", "eslint-plugin-wc": "^1.3.2",
"fancy-log": "^1.3.3", "fancy-log": "^1.3.3",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"gulp": "^4.0.2", "gulp": "^4.0.2",
@@ -198,7 +201,8 @@
"gulp-zopfli-green": "^3.0.1", "gulp-zopfli-green": "^3.0.1",
"html-minifier": "^4.0.0", "html-minifier": "^4.0.0",
"husky": "^1.3.1", "husky": "^1.3.1",
"lint-staged": "^11.0.1", "instant-mocha": "^1.3.1",
"lint-staged": "^11.1.2",
"lit-analyzer": "^1.2.1", "lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"magic-string": "^0.25.7", "magic-string": "^0.25.7",
@@ -207,7 +211,7 @@
"mocha": "^8.4.0", "mocha": "^8.4.0",
"object-hash": "^2.0.3", "object-hash": "^2.0.3",
"open": "^7.0.4", "open": "^7.0.4",
"prettier": "^2.3.2", "prettier": "^2.4.1",
"require-dir": "^1.2.0", "require-dir": "^1.2.0",
"rollup": "^2.8.2", "rollup": "^2.8.2",
"rollup-plugin-string": "^3.0.0", "rollup-plugin-string": "^3.0.0",
@@ -217,24 +221,26 @@
"sinon": "^11.0.0", "sinon": "^11.0.0",
"source-map-url": "^0.4.0", "source-map-url": "^0.4.0",
"systemjs": "^6.3.2", "systemjs": "^6.3.2",
"terser-webpack-plugin": "^5.1.4", "terser-webpack-plugin": "^5.2.4",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"ts-mocha": "^8.0.0", "typescript": "^4.4.3",
"typescript": "^4.3.5",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0", "vinyl-source-stream": "^2.0.0",
"webpack": "^5.43.0", "webpack": "^5.55.1",
"webpack-cli": "^4.7.2", "webpack-cli": "^4.8.0",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^4.3.0",
"webpack-manifest-plugin": "^3.1.1", "webpack-manifest-plugin": "^4.0.2",
"webpackbar": "^5.0.0-3",
"workbox-build": "^6.1.5" "workbox-build": "^6.1.5"
}, },
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch", "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": { "resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch", "@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10", "@webcomponents/webcomponentsjs": "^2.2.10",
"lit-html": "2.0.0-rc.3", "lit": "^2.0.0",
"lit-element": "3.0.0-rc.2" "lit-html": "2.0.0",
"lit-element": "3.0.0",
"@lit/reactive-element": "1.0.0"
}, },
"main": "src/home-assistant.js", "main": "src/home-assistant.js",
"husky": { "husky": {

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name="home-assistant-frontend", name="home-assistant-frontend",
version="20210729.0", version="20210930.0",
description="The Home Assistant frontend", description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend", url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors", author="The Home Assistant Authors",

View File

@@ -194,7 +194,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
this._state = "error"; this._state = "error";
this._errorMessage = data.message; this._errorMessage = data.message;
} }
} catch (err) { } catch (err: any) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error("Error starting auth flow", err); console.error("Error starting auth flow", err);
this._state = "error"; this._state = "error";
@@ -317,7 +317,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
return; return;
} }
await this._updateStep(newStep); await this._updateStep(newStep);
} catch (err) { } catch (err: any) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error("Error submitting step", err); console.error("Error submitting step", err);
this._state = "error"; this._state = "error";

View File

@@ -8,10 +8,6 @@ import {
AuthUrlSearchParams, AuthUrlSearchParams,
fetchAuthProviders, fetchAuthProviders,
} from "../data/auth"; } from "../data/auth";
import {
DiscoveryInformation,
fetchDiscoveryInformation,
} from "../data/discovery";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin"; import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { registerServiceWorker } from "../util/register-service-worker"; import { registerServiceWorker } from "../util/register-service-worker";
import "./ha-auth-flow"; import "./ha-auth-flow";
@@ -29,8 +25,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@state() private _authProviders?: AuthProvider[]; @state() private _authProviders?: AuthProvider[];
@state() private _discovery?: DiscoveryInformation;
constructor() { constructor() {
super(); super();
this.translationFragment = "page-authorize"; this.translationFragment = "page-authorize";
@@ -58,17 +52,14 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
// the name with a bold tag. // the name with a bold tag.
const loggingInWith = document.createElement("div"); const loggingInWith = document.createElement("div");
loggingInWith.innerText = this.localize( loggingInWith.innerText = this.localize(
this._discovery?.location_name "ui.panel.page-authorize.logging_in_with",
? "ui.panel.page-authorize.logging_in_to_with"
: "ui.panel.page-authorize.logging_in_with",
"locationName",
"LOCATION",
"authProviderName", "authProviderName",
"NAME" "NAME"
); );
loggingInWith.innerHTML = loggingInWith.innerHTML loggingInWith.innerHTML = loggingInWith.innerHTML.replace(
.replace("**LOCATION**", `<b>${this._discovery?.location_name}</b>`) "**NAME**",
.replace("**NAME**", `<b>${this._authProvider!.name}</b>`); `<b>${this._authProvider!.name}</b>`
);
const inactiveProviders = this._authProviders.filter( const inactiveProviders = this._authProviders.filter(
(prv) => prv !== this._authProvider (prv) => prv !== this._authProvider
@@ -85,20 +76,20 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
${loggingInWith} ${loggingInWith}
<ha-auth-flow <ha-auth-flow
.resources="${this.resources}" .resources=${this.resources}
.clientId="${this.clientId}" .clientId=${this.clientId}
.redirectUri="${this.redirectUri}" .redirectUri=${this.redirectUri}
.oauth2State="${this.oauth2State}" .oauth2State=${this.oauth2State}
.authProvider="${this._authProvider}" .authProvider=${this._authProvider}
></ha-auth-flow> ></ha-auth-flow>
${inactiveProviders.length > 0 ${inactiveProviders.length > 0
? html` ? html`
<ha-pick-auth-provider <ha-pick-auth-provider
.resources="${this.resources}" .resources=${this.resources}
.clientId="${this.clientId}" .clientId=${this.clientId}
.authProviders="${inactiveProviders}" .authProviders=${inactiveProviders}
@pick-auth-provider="${this._handleAuthProviderPick}" @pick-auth-provider=${this._handleAuthProviderPick}
></ha-pick-auth-provider> ></ha-pick-auth-provider>
` `
: ""} : ""}
@@ -108,7 +99,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this._fetchAuthProviders(); this._fetchAuthProviders();
this._fetchDiscoveryInfo();
if (matchMedia("(prefers-color-scheme: dark)").matches) { if (matchMedia("(prefers-color-scheme: dark)").matches) {
applyThemesOnElement( applyThemesOnElement(
@@ -144,10 +134,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
} }
} }
private async _fetchDiscoveryInfo() {
this._discovery = await fetchDiscoveryInformation();
}
private async _fetchAuthProviders() { private async _fetchAuthProviders() {
// Fetch auth providers // Fetch auth providers
try { try {
@@ -172,7 +158,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
this._authProviders = authProviders; this._authProviders = authProviders;
this._authProvider = authProviders[0]; this._authProvider = authProviders[0];
} catch (err) { } catch (err: any) {
// eslint-disable-next-line // eslint-disable-next-line
console.error("Error loading auth providers", err); console.error("Error loading auth providers", err);
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable lit/prefer-static-styles */
import { html, LitElement, TemplateResult } from "lit"; import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";

View File

@@ -1,6 +1,6 @@
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import { html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { property } from "lit/decorators"; import { property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import "../components/ha-icon-next"; import "../components/ha-icon-next";
@@ -18,14 +18,6 @@ class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {
protected render() { protected render() {
return html` return html`
<style>
paper-item {
cursor: pointer;
}
p {
margin-top: 0;
}
</style>
<p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p> <p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p>
${this.authProviders.map( ${this.authProviders.map(
(provider) => html` (provider) => html`
@@ -41,5 +33,14 @@ class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {
private _handlePick(ev) { private _handlePick(ev) {
fireEvent(this, "pick-auth-provider", ev.currentTarget.auth_provider); fireEvent(this, "pick-auth-provider", ev.currentTarget.auth_provider);
} }
static styles = css`
paper-item {
cursor: pointer;
}
p {
margin-top: 0;
}
`;
} }
customElements.define("ha-pick-auth-provider", HaPickAuthProvider); customElements.define("ha-pick-auth-provider", HaPickAuthProvider);

View File

@@ -33,7 +33,7 @@ export function saveTokens(tokens: AuthData | null) {
if (tokenCache.writeEnabled) { if (tokenCache.writeEnabled) {
try { try {
storage.hassTokens = JSON.stringify(tokens); storage.hassTokens = JSON.stringify(tokens);
} catch (err) { } catch (err: any) {
// write failed, ignore it. Happens if storage is full or private mode. // write failed, ignore it. Happens if storage is full or private mode.
} }
} }
@@ -58,7 +58,7 @@ export function loadTokens() {
} else { } else {
tokenCache.tokens = null; tokenCache.tokens = null;
} }
} catch (err) { } catch (err: any) {
tokenCache.tokens = null; tokenCache.tokens = null;
} }
} }

View File

@@ -1,5 +1,5 @@
export const COLORS = [ export const COLORS = [
"#377eb8", "#44739e",
"#984ea3", "#984ea3",
"#00d2d5", "#00d2d5",
"#ff7f00", "#ff7f00",

View File

@@ -56,19 +56,31 @@ export const FIXED_DOMAIN_ICONS = {
}; };
export const FIXED_DEVICE_CLASS_ICONS = { export const FIXED_DEVICE_CLASS_ICONS = {
aqi: "hass:air-filter",
current: "hass:current-ac", current: "hass:current-ac",
carbon_dioxide: "mdi:molecule-co2", carbon_dioxide: "mdi:molecule-co2",
carbon_monoxide: "mdi:molecule-co", carbon_monoxide: "mdi:molecule-co",
date: "hass:calendar",
energy: "hass:lightning-bolt", energy: "hass:lightning-bolt",
gas: "hass:gas-cylinder",
humidity: "hass:water-percent", humidity: "hass:water-percent",
illuminance: "hass:brightness-5", illuminance: "hass:brightness-5",
nitrogen_dioxide: "mdi:molecule",
nitrogen_monoxide: "mdi:molecule",
nitrous_oxide: "mdi:molecule",
ozone: "mdi:molecule",
temperature: "hass:thermometer", temperature: "hass:thermometer",
monetary: "mdi:cash", monetary: "mdi:cash",
pm25: "mdi:molecule",
pm1: "mdi:molecule",
pm10: "mdi:molecule",
pressure: "hass:gauge", pressure: "hass:gauge",
power: "hass:flash", power: "hass:flash",
power_factor: "hass:angle-acute", power_factor: "hass:angle-acute",
signal_strength: "hass:wifi", signal_strength: "hass:wifi",
sulphur_dioxide: "mdi:molecule",
timestamp: "hass:clock", timestamp: "hass:clock",
volatile_organic_compounds: "mdi:molecule",
voltage: "hass:sine-wave", voltage: "hass:sine-wave",
}; };
@@ -157,66 +169,3 @@ export const UNIT_F = "°F";
/** Entity ID of the default view. */ /** Entity ID of the default view. */
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view"; export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
/** HA Color Pallete. */
export const HA_COLOR_PALETTE = [
"ff0029",
"66a61e",
"377eb8",
"984ea3",
"00d2d5",
"ff7f00",
"af8d00",
"7f80cd",
"b3e900",
"c42e60",
"a65628",
"f781bf",
"8dd3c7",
"bebada",
"fb8072",
"80b1d3",
"fdb462",
"fccde5",
"bc80bd",
"ffed6f",
"c4eaff",
"cf8c00",
"1b9e77",
"d95f02",
"e7298a",
"e6ab02",
"a6761d",
"0097ff",
"00d067",
"f43600",
"4ba93b",
"5779bb",
"927acc",
"97ee3f",
"bf3947",
"9f5b00",
"f48758",
"8caed6",
"f2b94f",
"eff26e",
"e43872",
"d9b100",
"9d7a00",
"698cff",
"d9d9d9",
"00d27e",
"d06800",
"009f82",
"c49200",
"cbe8ff",
"fecddf",
"c27eb6",
"8cd2ce",
"c4b8d9",
"f883b0",
"a49100",
"f48800",
"27d0df",
"a04a9b",
];

View File

@@ -1,34 +0,0 @@
// Check for support of native locale string options
function checkToLocaleDateStringSupportsOptions() {
try {
new Date().toLocaleDateString("i");
} catch (e) {
return e.name === "RangeError";
}
return false;
}
function checkToLocaleTimeStringSupportsOptions() {
try {
new Date().toLocaleTimeString("i");
} catch (e) {
return e.name === "RangeError";
}
return false;
}
function checkToLocaleStringSupportsOptions() {
try {
new Date().toLocaleString("i");
} catch (e) {
return e.name === "RangeError";
}
return false;
}
export const toLocaleDateStringSupportsOptions =
checkToLocaleDateStringSupportsOptions();
export const toLocaleTimeStringSupportsOptions =
checkToLocaleTimeStringSupportsOptions();
export const toLocaleStringSupportsOptions =
checkToLocaleStringSupportsOptions();

View File

@@ -0,0 +1,31 @@
import { HaDurationData } from "../../components/ha-duration-input";
import { ForDict } from "../../data/automation";
export const createDurationData = (
duration: string | number | ForDict | undefined
): HaDurationData => {
if (duration === undefined) {
return {};
}
if (typeof duration !== "object") {
if (typeof duration === "string" || isNaN(duration)) {
const parts = duration?.toString().split(":") || [];
return {
hours: Number(parts[0]) || 0,
minutes: Number(parts[1]) || 0,
seconds: Number(parts[2]) || 0,
milliseconds: Number(parts[3]) || 0,
};
}
return { seconds: duration };
}
const { days, minutes, seconds, milliseconds } = duration;
let hours = duration.hours || 0;
hours = (hours || 0) + (days || 0) * 24;
return {
hours,
minutes,
seconds,
milliseconds,
};
};

View File

@@ -1,34 +1,14 @@
import { format } from "fecha";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation"; import { FrontendLocaleData } from "../../data/translation";
import { toLocaleDateStringSupportsOptions } from "./check_options_support"; import { polyfillsLoaded } from "../translations/localize";
const formatDateMem = memoizeOne( if (__BUILD__ === "latest" && polyfillsLoaded) {
(locale: FrontendLocaleData) => await polyfillsLoaded;
new Intl.DateTimeFormat(locale.language, { }
year: "numeric",
month: "long",
day: "numeric",
})
);
export const formatDate = toLocaleDateStringSupportsOptions // Tuesday, August 10
? (dateObj: Date, locale: FrontendLocaleData) => export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMem(locale).format(dateObj) formatDateWeekdayMem(locale).format(dateObj);
: (dateObj: Date) => format(dateObj, "longDate");
const formatDateShortMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
day: "numeric",
month: "short",
})
);
export const formatDateShort = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateShortMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "shortDate");
const formatDateWeekdayMem = memoizeOne( const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
@@ -39,7 +19,76 @@ const formatDateWeekdayMem = memoizeOne(
}) })
); );
export const formatDateWeekday = toLocaleDateStringSupportsOptions // August 10, 2021
? (dateObj: Date, locale: FrontendLocaleData) => export const formatDate = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateWeekdayMem(locale).format(dateObj) formatDateMem(locale).format(dateObj);
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
const formatDateMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
})
);
// 10/08/2021
export const formatDateNumeric = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateNumericMem(locale).format(dateObj);
const formatDateNumericMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "numeric",
day: "numeric",
})
);
// Aug 10
export const formatDateShort = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateShortMem(locale).format(dateObj);
const formatDateShortMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
day: "numeric",
month: "short",
})
);
// August 2021
export const formatDateMonthYear = (
dateObj: Date,
locale: FrontendLocaleData
) => formatDateMonthYearMem(locale).format(dateObj);
const formatDateMonthYearMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
year: "numeric",
})
);
// August
export const formatDateMonth = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMonthMem(locale).format(dateObj);
const formatDateMonthMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
})
);
// 2021
export const formatDateYear = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateYearMem(locale).format(dateObj);
const formatDateYearMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
})
);

View File

@@ -1,8 +1,15 @@
import { format } from "fecha";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation"; import { FrontendLocaleData } from "../../data/translation";
import { toLocaleStringSupportsOptions } from "./check_options_support";
import { useAmPm } from "./use_am_pm"; import { useAmPm } from "./use_am_pm";
import { polyfillsLoaded } from "../translations/localize";
if (__BUILD__ === "latest" && polyfillsLoaded) {
await polyfillsLoaded;
}
// August 9, 2021, 8:23 AM
export const formatDateTime = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeMem(locale).format(dateObj);
const formatDateTimeMem = memoizeOne( const formatDateTimeMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
@@ -10,17 +17,17 @@ const formatDateTimeMem = memoizeOne(
year: "numeric", year: "numeric",
month: "long", month: "long",
day: "numeric", day: "numeric",
hour: "numeric", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
hour12: useAmPm(locale), hour12: useAmPm(locale),
}) })
); );
export const formatDateTime = toLocaleStringSupportsOptions // August 9, 2021, 8:23:15 AM
? (dateObj: Date, locale: FrontendLocaleData) => export const formatDateTimeWithSeconds = (
formatDateTimeMem(locale).format(dateObj) dateObj: Date,
: (dateObj: Date, locale: FrontendLocaleData) => locale: FrontendLocaleData
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : ""); ) => formatDateTimeWithSecondsMem(locale).format(dateObj);
const formatDateTimeWithSecondsMem = memoizeOne( const formatDateTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
@@ -28,15 +35,27 @@ const formatDateTimeWithSecondsMem = memoizeOne(
year: "numeric", year: "numeric",
month: "long", month: "long",
day: "numeric", day: "numeric",
hour: "numeric", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
second: "2-digit", second: "2-digit",
hour12: useAmPm(locale), hour12: useAmPm(locale),
}) })
); );
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions // 9/8/2021, 8:23 AM
? (dateObj: Date, locale: FrontendLocaleData) => export const formatDateTimeNumeric = (
formatDateTimeWithSecondsMem(locale).format(dateObj) dateObj: Date,
: (dateObj: Date, locale: FrontendLocaleData) => locale: FrontendLocaleData
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : ""); ) => formatDateTimeNumericMem(locale).format(dateObj);
const formatDateTimeNumericMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: useAmPm(locale),
})
);

View File

@@ -1,8 +1,15 @@
import { format } from "fecha";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation"; import { FrontendLocaleData } from "../../data/translation";
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
import { useAmPm } from "./use_am_pm"; import { useAmPm } from "./use_am_pm";
import { polyfillsLoaded } from "../translations/localize";
if (__BUILD__ === "latest" && polyfillsLoaded) {
await polyfillsLoaded;
}
// 9:15 PM || 21:15
export const formatTime = (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeMem(locale).format(dateObj);
const formatTimeMem = memoizeOne( const formatTimeMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
@@ -13,40 +20,32 @@ const formatTimeMem = memoizeOne(
}) })
); );
export const formatTime = toLocaleTimeStringSupportsOptions // 9:15:24 PM || 21:15:24
? (dateObj: Date, locale: FrontendLocaleData) => export const formatTimeWithSeconds = (
formatTimeMem(locale).format(dateObj) dateObj: Date,
: (dateObj: Date, locale: FrontendLocaleData) => locale: FrontendLocaleData
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : ""); ) => formatTimeWithSecondsMem(locale).format(dateObj);
const formatTimeWithSecondsMem = memoizeOne( const formatTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
hour: "numeric", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
second: "2-digit", second: "2-digit",
hour12: useAmPm(locale), hour12: useAmPm(locale),
}) })
); );
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions // Tuesday 7:00 PM || Tuesday 19:00
? (dateObj: Date, locale: FrontendLocaleData) => export const formatTimeWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWithSecondsMem(locale).format(dateObj) formatTimeWeekdayMem(locale).format(dateObj);
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
const formatTimeWeekdayMem = memoizeOne( const formatTimeWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
weekday: "long", hour: useAmPm(locale) ? "numeric" : "2-digit",
hour: "numeric",
minute: "2-digit", minute: "2-digit",
second: "2-digit",
hour12: useAmPm(locale), hour12: useAmPm(locale),
}) })
); );
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWeekdayMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");

View File

@@ -1,48 +1,32 @@
import { LocalizeFunc } from "../translations/localize"; import { selectUnit } from "@formatjs/intl-utils";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { polyfillsLoaded } from "../translations/localize";
/** if (__BUILD__ === "latest" && polyfillsLoaded) {
* Calculate a string representing a date object as relative time from now. await polyfillsLoaded;
*
* Example output: 5 minutes ago, in 3 days.
*/
const tests = [60, 60, 24, 7];
const langKey = ["second", "minute", "hour", "day"];
export default function relativeTime(
dateObj: Date,
localize: LocalizeFunc,
options: {
compareTime?: Date;
includeTense?: boolean;
} = {}
): string {
const compareTime = options.compareTime || new Date();
let delta = (compareTime.getTime() - dateObj.getTime()) / 1000;
const tense = delta >= 0 ? "past" : "future";
delta = Math.abs(delta);
let roundedDelta = Math.round(delta);
if (roundedDelta === 0) {
return localize("ui.components.relative_time.just_now");
}
let unit = "week";
for (let i = 0; i < tests.length; i++) {
if (roundedDelta < tests[i]) {
unit = langKey[i];
break;
}
delta /= tests[i];
roundedDelta = Math.round(delta);
}
return localize(
options.includeTense === false
? `ui.components.relative_time.duration.${unit}`
: `ui.components.relative_time.${tense}_duration.${unit}`,
"count",
roundedDelta
);
} }
const formatRelTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
// @ts-expect-error
new Intl.RelativeTimeFormat(locale.language, { numeric: "auto" })
);
export const relativeTime = (
from: Date,
locale: FrontendLocaleData,
to?: Date,
includeTense = true
): string => {
const diff = selectUnit(from, to);
if (includeTense) {
return formatRelTimeMem(locale).format(diff.value, diff.unit);
}
return Intl.NumberFormat(locale.language, {
style: "unit",
// @ts-expect-error
unit: diff.unit,
unitDisplay: "long",
}).format(Math.abs(diff.value));
};

View File

@@ -1,6 +1,7 @@
import memoizeOne from "memoize-one";
import { FrontendLocaleData, TimeFormat } from "../../data/translation"; import { FrontendLocaleData, TimeFormat } from "../../data/translation";
export const useAmPm = (locale: FrontendLocaleData): boolean => { export const useAmPm = memoizeOne((locale: FrontendLocaleData): boolean => {
if ( if (
locale.time_format === TimeFormat.language || locale.time_format === TimeFormat.language ||
locale.time_format === TimeFormat.system locale.time_format === TimeFormat.system
@@ -12,4 +13,4 @@ export const useAmPm = (locale: FrontendLocaleData): boolean => {
} }
return locale.time_format === TimeFormat.am_pm; return locale.time_format === TimeFormat.am_pm;
}; });

View File

@@ -74,7 +74,7 @@ class Storage {
this._storage[storageKey] = value; this._storage[storageKey] = value;
try { try {
window.localStorage.setItem(storageKey, JSON.stringify(value)); window.localStorage.setItem(storageKey, JSON.stringify(value));
} catch (err) { } catch (err: any) {
// Safari in private mode doesn't allow localstorage // Safari in private mode doesn't allow localstorage
} }
} }

View File

@@ -167,7 +167,7 @@ const processTheme = (
const prefixedRgbKey = `--${rgbKey}`; const prefixedRgbKey = `--${rgbKey}`;
styles[prefixedRgbKey] = rgbValue; styles[prefixedRgbKey] = rgbValue;
keys[prefixedRgbKey] = ""; keys[prefixedRgbKey] = "";
} catch (e) { } catch (err: any) {
continue; continue;
} }
} }

View File

@@ -5,4 +5,4 @@ export const mainWindow =
? window ? window
: parent.name === MAIN_WINDOW_NAME : parent.name === MAIN_WINDOW_NAME
? parent ? parent
: top; : top!;

View File

@@ -0,0 +1,24 @@
/** Return an icon representing a alarm panel state. */
export const alarmPanelIcon = (state?: string) => {
switch (state) {
case "armed_away":
return "hass:shield-lock";
case "armed_vacation":
return "hass:shield-airplane";
case "armed_home":
return "hass:shield-home";
case "armed_night":
return "hass:shield-moon";
case "armed_custom_bypass":
return "hass:security";
case "pending":
return "hass:shield-outline";
case "triggered":
return "hass:bell-ring";
case "disarmed":
return "hass:shield-off";
default:
return "hass:shield";
}
};

View File

@@ -22,8 +22,9 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
case "gas": case "gas":
case "problem": case "problem":
case "safety": case "safety":
case "smoke":
return is_off ? "hass:check-circle" : "hass:alert-circle"; return is_off ? "hass:check-circle" : "hass:alert-circle";
case "smoke":
return is_off ? "hass:check-circle" : "hass:smoke";
case "heat": case "heat":
return is_off ? "hass:thermometer" : "hass:fire"; return is_off ? "hass:thermometer" : "hass:fire";
case "light": case "light":
@@ -44,6 +45,8 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
return is_off ? "hass:home-outline" : "hass:home"; return is_off ? "hass:home-outline" : "hass:home";
case "sound": case "sound":
return is_off ? "hass:music-note-off" : "hass:music-note"; return is_off ? "hass:music-note-off" : "hass:music-note";
case "update":
return is_off ? "mdi:package" : "mdi:package-up";
case "vibration": case "vibration":
return is_off ? "hass:crop-portrait" : "hass:vibrate"; return is_off ? "hass:crop-portrait" : "hass:vibrate";
case "window": case "window":

View File

@@ -4,7 +4,7 @@ import { FrontendLocaleData } from "../../data/translation";
import { formatDate } from "../datetime/format_date"; import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time"; import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time"; import { formatTime } from "../datetime/format_time";
import { formatNumber } from "../string/format_number"; import { formatNumber } from "../number/format_number";
import { LocalizeFunc } from "../translations/localize"; import { LocalizeFunc } from "../translations/localize";
import { computeStateDomain } from "./compute_state_domain"; import { computeStateDomain } from "./compute_state_domain";

View File

@@ -5,6 +5,7 @@ import { HassEntity } from "home-assistant-js-websocket";
* Optionally pass in a state to influence the domain icon. * Optionally pass in a state to influence the domain icon.
*/ */
import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const"; import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const";
import { alarmPanelIcon } from "./alarm_panel_icon";
import { binarySensorIcon } from "./binary_sensor_icon"; import { binarySensorIcon } from "./binary_sensor_icon";
import { coverIcon } from "./cover_icon"; import { coverIcon } from "./cover_icon";
import { sensorIcon } from "./sensor_icon"; import { sensorIcon } from "./sensor_icon";
@@ -18,18 +19,7 @@ export const domainIcon = (
switch (domain) { switch (domain) {
case "alarm_control_panel": case "alarm_control_panel":
switch (compareState) { return alarmPanelIcon(compareState);
case "armed_home":
return "hass:bell-plus";
case "armed_night":
return "hass:bell-sleep";
case "disarmed":
return "hass:bell-outline";
case "triggered":
return "hass:bell-ring";
default:
return "hass:bell";
}
case "binary_sensor": case "binary_sensor":
return binarySensorIcon(compareState, stateObj); return binarySensorIcon(compareState, stateObj);

View File

@@ -2,6 +2,7 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const"; import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
import { batteryIcon } from "./battery_icon"; import { batteryIcon } from "./battery_icon";
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
export const sensorIcon = (stateObj?: HassEntity): string | undefined => { export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
const dclass = stateObj?.attributes.device_class; const dclass = stateObj?.attributes.device_class;
@@ -10,7 +11,7 @@ export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
return FIXED_DEVICE_CLASS_ICONS[dclass]; return FIXED_DEVICE_CLASS_ICONS[dclass];
} }
if (dclass === "battery") { if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
return stateObj ? batteryIcon(stateObj) : "hass:battery"; return stateObj ? batteryIcon(stateObj) : "hass:battery";
} }

View File

@@ -1,4 +1,5 @@
import { FrontendLocaleData, NumberFormat } from "../../data/translation"; import { FrontendLocaleData, NumberFormat } from "../../data/translation";
import { round } from "./round";
export const numberFormatToLocale = ( export const numberFormatToLocale = (
localeOptions: FrontendLocaleData localeOptions: FrontendLocaleData
@@ -50,17 +51,22 @@ export const formatNumber = (
locale, locale,
getDefaultFormatOptions(num, options) getDefaultFormatOptions(num, options)
).format(Number(num)); ).format(Number(num));
} catch (error) { } catch (err: any) {
// Don't fail when using "TEST" language // Don't fail when using "TEST" language
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(error); console.error(err);
return new Intl.NumberFormat( return new Intl.NumberFormat(
undefined, undefined,
getDefaultFormatOptions(num, options) getDefaultFormatOptions(num, options)
).format(Number(num)); ).format(Number(num));
} }
} }
return num.toString(); if (typeof num === "string") {
return num;
}
return `${round(num, options?.maximumFractionDigits).toString()}${
options?.style === "currency" ? ` ${options.currency}` : ""
}`;
}; };
/** /**
@@ -72,7 +78,10 @@ const getDefaultFormatOptions = (
num: string | number, num: string | number,
options?: Intl.NumberFormatOptions options?: Intl.NumberFormatOptions
): Intl.NumberFormatOptions => { ): Intl.NumberFormatOptions => {
const defaultOptions: Intl.NumberFormatOptions = options || {}; const defaultOptions: Intl.NumberFormatOptions = {
maximumFractionDigits: 2,
...options,
};
if (typeof num !== "string") { if (typeof num !== "string") {
return defaultOptions; return defaultOptions;

View File

@@ -1,4 +1,4 @@
export const compare = (a: string, b: string) => { export const stringCompare = (a: string, b: string) => {
if (a < b) { if (a < b) {
return -1; return -1;
} }
@@ -9,5 +9,5 @@ export const compare = (a: string, b: string) => {
return 0; return 0;
}; };
export const caseInsensitiveCompare = (a: string, b: string) => export const caseInsensitiveStringCompare = (a: string, b: string) =>
compare(a.toLowerCase(), b.toLowerCase()); stringCompare(a.toLowerCase(), b.toLowerCase());

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