Compare commits

..

377 Commits

Author SHA1 Message Date
Ludeeus d1605ba196 Add move data disk 2021-09-16 15:15:57 +00: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
Paulus Schoutsen 749079c1c3 Bumped version to 20210729.0 2021-07-29 12:02:34 -07:00
Bram Kragten ae10ff42e1 Update state-history-chart-line.ts 2021-07-29 20:45:25 +02:00
Bram Kragten d4cbdab4a3 Fix energy calculations (#9647)
* Fix calculations

* max.. not min...
2021-07-29 18:29:49 +00:00
Bram Kragten 1bd6392a4c Add text when no statistics found (#9642)
* Add text when no statistics found

* Update src/components/entity/ha-statistic-picker.ts

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

* fix typos

* Update src/components/entity/ha-statistic-picker.ts

* Prettier

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-07-29 20:26:22 +02:00
Bram Kragten 1531e99528 Energy dashboard tweaks and fixes (#9643)
* Energy dashboard tweaks and fixes

* Make headers smaller

* Change button styling in onboarding

* Disable add when no stat choosen

* Oops

* Update hui-energy-carbon-consumed-gauge-card.ts

* Update hui-energy-distribution-card.ts
2021-07-29 10:44:33 -07:00
Julien Ehrhart 6e7af18494 Add support for DEVICE_CLASS_MONETARY (#9640) 2021-07-29 15:20:12 +00:00
Paulus Schoutsen e1bae65aeb Remove currency from initial config (#9638) 2021-07-29 17:12:49 +02:00
Paulus Schoutsen 65bfdf94c9 Bumped version to 20210728.0 2021-07-28 11:05:28 -07:00
Paulus Schoutsen 9a92825954 Move energy panel up in sidebar (#9636)
* Move energy panel up in sidebar

* Remove headers from wizard

* Update text

* Always show all configured devices

* Make leaf clickable

* Bump HAWS
2021-07-28 11:05:00 -07:00
Paulus Schoutsen 469faf509b Remove the energy summary card since it's already in other cards (#9635) 2021-07-28 10:46:59 -07:00
Paulus Schoutsen f87d4a5ab6 If we have solar defined, always make sure sum is number so it shows placeholders (#9634) 2021-07-28 10:46:51 -07:00
Bram Kragten 9c31b749d7 Format monetary sensor as currency (#9633) 2021-07-28 09:34:03 -07:00
Paulus Schoutsen 521d5df064 Show solar forecast even if no prod stats available (#9632) 2021-07-28 09:20:31 -07:00
Paulus Schoutsen 4d330fba8a Use historic CO2 data into account (#9626)
* Use historic CO2 data into account

* Also add to gauge

* Apply suggestions from code review

* Format

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-28 09:20:06 -07:00
Bram Kragten 2e04a55d5c Add statistics card to card picker (#9631)
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
2021-07-28 08:41:18 -07:00
Bram Kragten 0d9d0aa18b Add legend to electricity graph, fix text color in table (#9629) 2021-07-28 08:40:43 -07:00
Bram Kragten f8dd1795bc Add animation to distribution (#9625) 2021-07-28 08:39:12 -07:00
Bram Kragten 1d7007584c Use currency from core config (#9628) 2021-07-28 08:38:04 -07:00
Charles Garwood 8cd9f891fb Add remove failed node support to Z-Wave JS devices (#9560)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-28 13:38:50 +00:00
Franck Nijhof 6ab0f1db57 Add currency core configuration (#9620)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-07-28 12:48:39 +02:00
Paulus Schoutsen 37d754d069 Bumped version to 20210727.0 2021-07-27 15:25:12 -07:00
Charles Garwood e12b194d41 Add Z-Wave JS Heal Node wizard (#9562) 2021-07-28 00:16:19 +02:00
Michelle Fuchs 07d7fa26fe Show value of 'Before'-time in automation editor #9142 (#9598) 2021-07-28 00:13:38 +02:00
Bram Kragten 73b9b87ef3 Update energy dashboard (#9624) 2021-07-27 13:57:09 -07:00
Bram Kragten 0c0091375c Fix grid onboarding (#9621) 2021-07-27 21:34:43 +02:00
Bram Kragten 21a29ed3a5 Statistic sums requires at least 2 values (#9616) 2021-07-26 22:47:50 +00:00
Bram Kragten a6312f4279 Fix sidebar view edit mode (#9615) 2021-07-26 22:11:34 +00:00
Bram Kragten f459abdf85 Fix statistics graph (#9607) 2021-07-26 15:03:31 -07:00
Bram Kragten 586fa1d0f0 Fix zoom setting in maps card (#9613)
Fixes #9611
2021-07-26 15:02:50 -07:00
Bram Kragten bf4192a1f0 Bump Vaadin elements (#9609) 2021-07-26 15:02:23 -07:00
Bram Kragten ac31eedf65 Bump material elements (#9610) 2021-07-26 15:02:01 -07:00
Bram Kragten b05dc5141c Fix chart tooltip footer always rendering (#9614) 2021-07-26 15:01:32 -07:00
Raman Gupta 32c6fb14dd Re-add success/failure indicator on call service button in dev tools (#9600)
* Re-add success/failure indicator on call service button in dev tools

* move success outside of try block

* Export HaProgressButton
2021-07-26 23:59:49 +02:00
Raman Gupta 982c940381 Try to fix download of zwave_js logs (#9612) 2021-07-26 23:26:22 +02:00
Bram Kragten a7a8aaa887 Merge pull request #9608 from home-assistant/dev
20210726.0
2021-07-26 23:04:05 +02:00
Philip Allgaier bf83a9980e Add bottom margin to <ha-map> in more-info-person (#9604) 2021-07-26 22:45:23 +02:00
Bram Kragten 11be603ed3 Bumped version to 20210726.0 2021-07-26 22:18:41 +02:00
Bram Kragten a432cf8405 Fix selected icon of picker elements (#9606) 2021-07-26 22:17:29 +02:00
Bram Kragten 9dd6b3b72d Add Energy panel (#9445)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-07-26 09:57:59 -07:00
Thomas Lovén faca62b55f Avoid gaps in grid card with conditional (#9507)
* Avoid gaps in grid card with conditional. Fix #9433

* Rename hidden parameter

* Restore devcontainer.json

* Fix accidental change

* Update src/panels/lovelace/cards/hui-grid-card.ts

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

* Prettier

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-26 14:43:14 +02:00
J. Nick Koston a339de89f5 Add support for lock unlocking,locking and jammed states (#9537) 2021-07-20 10:52:24 -10:00
Charles Garwood e40c90e9c0 Translate Z-Wave JS Re-Interview Device button (#9566) 2021-07-19 11:23:10 +02:00
Bram Kragten 3f447bb8a7 Bump Polymer (#9525)
* Bump Polymer

* Remove formatting from patch

* Cleanup

* Fix Vaadin

* Fix typing
2021-07-16 01:23:09 +02:00
Bram Kragten 21dca3fbf8 Use patch for @lit-labs/virtualizer (#9524)
* Use patch for `@lit-labs/virtualizer`

* Re-organize

* Use original for patch

* Also patch import of EventTarget polyfill

* Delete EventTarget-ponyfill.js

* Prettier
2021-07-16 01:16:24 +02:00
Bram Kragten 1078bb4287 Add statistics-graph-card (#9479)
* Add `statistics-graph-card`

* Make variable names clearer
2021-07-16 01:16:02 +02:00
Charles Garwood daeed06e70 Clean up Z-Wave JS dialog CSS (#9561) 2021-07-16 01:13:00 +02:00
Bram Kragten 1206e2d75f Change Data Entry Flow loading step description logic + cleanups (#9558)
* Change Data Entry Flow loading step description logic + cleanups

Fixes #6251

* Lint

* Address comment
2021-07-15 21:07:25 +02:00
Bram Kragten cc81239b9d Add struct for fire-dom-event action (#9556) 2021-07-15 12:08:33 +02:00
Bram Kragten e797c01761 Update lint rules (#9563)
Updated deps

Added `unused-imports`, prefer arrow, and import order
2021-07-15 12:08:04 +02:00
Charles Garwood 12f7366968 Replace home ID with config entry title in Z-Wave device Info (#9488) 2021-07-13 18:05:22 +02:00
Bram Kragten b7fd7abe85 increase memory demo action (#9553) 2021-07-13 10:15:59 +02:00
Jason Hunter a5e1f3d165 bump hls.js (#9546)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-13 09:00:51 +02:00
Joakim Sørensen 212d047ada Place Z-Wave JS stages in grid (#9548) 2021-07-12 12:44:07 -04:00
Bram Kragten 2e16127fde Inline mdi icons with babel plugin + bump build deps (#9498) 2021-07-12 09:26:07 -07:00
Charles Garwood e8b53a619d Show parameter number on ZWaveJS device config panel (#9494) 2021-07-12 09:41:54 +02:00
posixx df1ca1fd96 HA frontend change for alarm panel vacation mode (#8326)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-08 16:21:09 +02:00
Bram Kragten 8f85132d48 Download translations on release (#9530)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-07-08 13:04:38 +00:00
Marius 349144599c Change text on Yaml configuration reloading (#9529)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-08 13:04:17 +00:00
Thomas Lovén 979093923b Colorize trace paths for choose without explicit default case (#9527)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-07-08 00:15:20 +02:00
Thomas Lovén 137f8ad4cb Revert "Colorize trace paths for choose without explicit default case"
This reverts commit c1f462b8f8.
2021-07-07 21:39:33 +00:00
Thomas Lovén c1f462b8f8 Colorize trace paths for choose without explicit default case 2021-07-07 21:28:13 +00:00
Bram Kragten 6701c4c371 Dedupe dependencies (#9523)
+ yarn bump + CI check for duplicate dependencies
2021-07-07 17:31:37 +02:00
Bram Kragten fc6e459c09 Upgrade to yarn 2 (#9500) 2021-07-07 16:33:24 +02:00
Philip Allgaier 9e28b3447e Fixed "ID" spelling in trigger field label (#9518) 2021-07-07 11:07:57 +02:00
Bram Kragten 5c737e1969 Merge pull request #9517 from home-assistant/dev 2021-07-07 09:58:19 +02:00
Bram Kragten 569765e77e Bumped version to 20210707.0 2021-07-07 09:43:20 +02:00
Bram Kragten bc0d63ed12 Better fix for Safari IBD bug (#9514)
* Better fix for Safari IBD bug

* comment
2021-07-07 09:42:41 +02:00
GitHub Action 02f9893522 Translation update 2021-07-07 00:47:35 +00:00
Bram Kragten b4bbe63f0f Fix device trigger clearing trigger id (#9511) 2021-07-06 11:53:21 +02:00
Bram Kragten fabbcac99f Merge pull request #9510 from home-assistant/dev 2021-07-06 11:09:42 +02:00
Bram Kragten b1b5ab6949 Bumped version to 20210706.0 2021-07-06 10:47:41 +02:00
Bram Kragten 4b9487183b Add tracing to scripts (#9486) 2021-07-06 10:46:51 +02:00
Bram Kragten de5a817953 Add UI for trigger condition (#9505) 2021-07-06 10:43:07 +02:00
GitHub Action 4970f640fa Translation update 2021-07-06 00:47:02 +00:00
Bram Kragten 18996535b7 Fix race in translations loading (#9499) 2021-07-05 11:05:49 +02:00
GitHub Action 2a1e31b5e9 Translation update 2021-07-05 00:47:09 +00:00
GitHub Action 8ca9a0f409 Translation update 2021-07-04 00:47:10 +00:00
GitHub Action fcc89a67ba Translation update 2021-07-03 00:46:50 +00:00
GitHub Action 1f377d43c5 Translation update 2021-07-02 00:47:14 +00:00
Joakim Sørensen 30d6c68908 Fix writing supervisor entrypoint (#9489) 2021-07-01 11:34:22 +02:00
Bram Kragten dc781da93a Use ES5 build for Supervisor on Safari 12 and below (#9485) 2021-07-01 09:27:02 +02:00
Bram Kragten 36c20e4348 Limit height of charts to 400px (#9487) 2021-07-01 07:54:17 +02:00
GitHub Action 4466950bb8 Translation update 2021-07-01 00:47:12 +00:00
Joakim Sørensen be29828454 Change path for codespaces (#9484) 2021-06-30 15:58:01 +02:00
Bram Kragten 7bab245073 Merge pull request #9483 from home-assistant/dev 2021-06-30 12:17:54 +02:00
Bram Kragten f5dcf0b760 Bumped version to 20210630.0 2021-06-30 12:03:07 +02:00
Bram Kragten 8141f78a92 Use ES5 build for Safari 12 and below (#9482) 2021-06-30 12:02:01 +02:00
Joakim Sørensen be244b3d00 Rename hassos -> haos (#9477) 2021-06-30 12:00:33 +02:00
Bram Kragten 805f5ff9b6 Recreate columns if cards change (#9480) 2021-06-30 11:52:36 +02:00
Bram Kragten 76daeb7e55 Fix wait for not loaded panel (#9478) 2021-06-30 11:50:49 +02:00
GitHub Action 9594c8106e Translation update 2021-06-30 00:47:15 +00:00
Joakim Sørensen ed4052365c Allow placeholders in config and option flows (#9314) 2021-06-30 01:09:18 +02:00
Joakim Sørensen 377ebadc10 Show note about integrations not in UI even for non-advanced (#9457)
* Show note about integrations not in UI even for non-advanced

* Address comments
2021-06-30 01:08:58 +02:00
Charles Garwood ed4809b71e Handle config entry not loaded on Z-Wave JS config panel (#9451)
* Handle config entry not loaded on Z-Wave JS config panel

* Move ERROR_STATES to data/config_entries and import
2021-06-29 19:04:42 -04:00
Shane Qi db37dffdbb Fixed Logbook Card/Panel/Dialog Incorrect Entires for input_datetime Entities (#9399)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-30 00:32:19 +02:00
Bram Kragten 13cab7e301 Fix logbook card (#9476) 2021-06-29 18:32:51 +02:00
Charles Garwood 0a50fc66e5 Add ZWave JS heal network UI (#9449)
* Add Z-Wave JS heal network dialog

* progress bar tweak

* tweak package.json

* typing tweak

* Update yarn.lock

* Align versions

* address review comments

* Use indeterminate linear-progress instead of circular-progress

* cleanup circular-progress

* prettier

* additional review cleanup

* subscribe to status update if heal already running

* more cleanup

* more cleanup

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-29 11:14:50 -04:00
uvjustin a3d1a3566d Fix ha-hls-player cleanup for lit 2 (#9388)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-29 15:44:18 +02:00
Philip Allgaier ba0be927ed Set min value for history graph card rendered hours (#9464) 2021-06-29 15:01:42 +02:00
Philip Allgaier 4260606267 Set minimum = 1 hours to show (#9466) 2021-06-29 15:01:18 +02:00
Philip Allgaier 4665db4f27 Fix integration card rename dialog logic (#9467) 2021-06-29 15:00:27 +02:00
Franck Nijhof 43503ba085 Fix number entity row availability when state unknown (#9475) 2021-06-29 14:57:39 +02:00
Joakim Sørensen 0a83a704f1 Ignore previous versions in add-on changelog (#9474) 2021-06-29 14:57:25 +02:00
GitHub Action 08de941c90 Translation update 2021-06-29 00:47:16 +00:00
GitHub Action 62228ef144 Translation update 2021-06-28 00:47:17 +00:00
GitHub Action 9731257782 Translation update 2021-06-27 00:47:51 +00:00
GitHub Action 4ec9c9c16e Translation update 2021-06-26 00:46:57 +00:00
Franck Nijhof 45436731e2 Fix select entity disabled when no item selected (#9465) 2021-06-24 21:36:23 -07:00
GitHub Action 27730e65e7 Translation update 2021-06-25 00:47:31 +00:00
rianadon a4aba93d57 Add input elements to login page for password managers (#9369) 2021-06-24 23:14:36 +02:00
Martin Hjelmare d93db16963 Add button for zwave_js options flow (#9001)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-06-24 13:21:30 +02:00
GitHub Action c327fe11b8 Translation update 2021-06-23 00:48:18 +00:00
GitHub Action 4fbc31d0b0 Translation update 2021-06-22 00:48:29 +00:00
Bram Kragten 9a4a1cb4ec Fix charts tooltips and legends (#9448) 2021-06-21 10:38:50 +02:00
Joakim Sørensen 202d6957bc Allow clearing values in optional selects (#9442)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-21 09:41:38 +02:00
Joakim Sørensen 14fcff7774 Fix secrets schema (#9446) 2021-06-21 09:41:06 +02:00
GitHub Action 2c9aa1cab4 Translation update 2021-06-21 00:48:39 +00:00
GitHub Action 7745c10d07 Translation update 2021-06-20 00:48:37 +00:00
Franck Nijhof c1d571de42 Add Select entity (#9422)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-19 13:26:19 +02:00
GitHub Action cecb66451c Translation update 2021-06-19 00:48:32 +00:00
Bram Kragten 0ef3421fa2 Bump chartjs to version 3 (#9401) 2021-06-18 11:15:07 +02:00
GitHub Action f88e238d41 Translation update 2021-06-18 00:48:41 +00:00
Raman Gupta ce3c8f264d Update zwave_js log subscription to handle core changes (#9262) 2021-06-17 17:19:18 +02:00
Bram Kragten 9fbd594f37 Check if /proc/version exists (#9438) 2021-06-17 15:23:20 +02:00
Bram Kragten 5ad95cad90 Support white color mode (#9386) 2021-06-17 15:23:08 +02:00
GitHub Action 7e507b40c4 Translation update 2021-06-17 00:48:10 +00:00
Joakim Sørensen 446a9b5c02 Don't clear action on expected error (#9428) 2021-06-16 11:13:30 +02:00
GitHub Action e02e61384e Translation update 2021-06-16 00:48:25 +00:00
Raman Gupta 5deb570fdf Add button to download logs from zwave_js logs page (#9395) 2021-06-16 00:02:40 +02:00
Joakim Sørensen 915c46f144 Fix add-on configuration validation (#9424) 2021-06-15 21:00:28 +02:00
Philip Allgaier 30d6c5eaf3 Gracefully handle logbook retrieval errors (#9377) 2021-06-15 19:54:18 +02:00
Philip Allgaier 6e50d1166a Only attempt to get "trace/contexts" if admin (#9378)
* Only attempt to get "trace/contexts" if admin

* Changes from review
2021-06-15 16:38:37 +02:00
Philip Allgaier 0e3eed0563 Fix supervisor text "error_addon_not_started" (#9412) 2021-06-15 15:38:14 +02:00
Joakim Sørensen 1b1676cecc Use poll for webpack for WSL (#9425) 2021-06-15 15:34:15 +02:00
GitHub Action d911fe6a0b Translation update 2021-06-15 00:48:36 +00:00
Bram Kragten 22253a3385 Add header and description to progress options flow (#9423) 2021-06-15 01:10:24 +02:00
GitHub Action 38640c99e3 Translation update 2021-06-14 00:48:26 +00:00
GitHub Action d6df8bddea Translation update 2021-06-13 00:48:28 +00:00
GitHub Action ddfc4bd98e Translation update 2021-06-12 00:48:12 +00:00
Shane Qi 3d6674325c Fix the issue that logbook card doesn't translate context.user_id to name if it's a user's id. (#9383)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-10 14:51:24 +02:00
Bram Kragten 194829f5b1 Generalize map (#9331)
* Generalize map

* Fix path opacity

* Add fitZones
2021-06-10 14:22:44 +02:00
GitHub Action 11a77253f4 Translation update 2021-06-10 00:49:02 +00:00
GitHub Action 67be2343f8 Translation update 2021-06-09 00:48:19 +00:00
Joakim Sørensen e9b1b3d853 Fix issues with restoring snapshot during onboarding (#9385)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-08 17:57:53 +02:00
Bram Kragten 8a33d174d7 FIx selecting service/url path action after choosing default action (#9376)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-06-08 14:24:58 +02:00
Joakim Sørensen 226d6216b7 Build wheels for 3.9-alpine3.13 (#9390) 2021-06-08 13:07:02 +02:00
GitHub Action 1925bb01be Translation update 2021-06-08 00:49:18 +00:00
Bram Kragten 82a4806e01 Change line logic 2021-06-07 10:45:57 +02:00
Joakim Sørensen ce419fae7b Add password confirmation to snapshot creation (#9349)
* Add password confirmation to snapshot creation

* Remove confirm_password before sending

* change layout

* style changes

* Adjust styling

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-07 10:45:20 +02:00
Joakim Sørensen c68b76e2da Add hardware dialog (#9348)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-07 10:16:33 +02:00
Joakim Sørensen 342020b420 Fix downloads on mobile (#9375) 2021-06-07 10:15:43 +02:00
GitHub Action 1e6e99e3c7 Translation update 2021-06-07 00:49:13 +00:00
GitHub Action 2e9aafc377 Translation update 2021-06-06 00:49:09 +00:00
dependabot[bot] 299c863f49 Bump ws from 6.2.1 to 6.2.2 (#9372) 2021-06-05 23:52:13 +02:00
Bram Kragten c2792a28ba Move attributes down in more info person and timer (#9368) 2021-06-05 12:52:27 +02:00
GitHub Action 635a027a8e Translation update 2021-06-05 02:40:15 +00:00
Will Adler a45b8ca8e7 Add period to end of sentence (#9361) 2021-06-04 08:57:03 +02:00
GitHub Action 1e6e945a07 Translation update 2021-06-04 02:52:56 +00:00
Bram Kragten 8666e6baae Merge pull request #9358 from home-assistant/dev 2021-06-03 23:12:30 +02:00
Bram Kragten f71157c24d Remove tsc from pre commit (#9359) 2021-06-03 22:57:03 +02:00
Bram Kragten e87a2b36cf Bumped version to 20210603.0 2021-06-03 22:51:53 +02:00
Bram Kragten 5418474f64 Polyfill globalThis in latest build (#9352) 2021-06-03 22:50:33 +02:00
Philip Allgaier 8836ba6ceb Pick the correct backend-selected active theme (#9357) 2021-06-03 22:48:52 +02:00
Bram Kragten 509c5b497a Disable babel compact option (#9335) 2021-06-03 12:34:30 -07:00
Joakim Sørensen e00bcc9f48 Better exit navigation for my-ingress (#9342) 2021-06-03 10:01:12 -07:00
Joakim Sørensen bdef9fd040 Add missing media folder to snapshot (#9341) 2021-06-03 10:21:04 +02:00
GitHub Action c956491ec5 Translation update 2021-06-03 03:48:04 +00:00
Bram Kragten 68bc549d6a Use HLS light build (#9338)
* Use HLS light build

* Bump hls, backBufferLength
2021-06-02 18:34:18 +02:00
Bram Kragten 9c64eafc21 Fix ZHA visualization (#9337) 2021-06-02 18:33:55 +02:00
Bram Kragten b05e86d442 Fix noUnderline in search input (#9339) 2021-06-02 18:33:44 +02:00
Bram Kragten fe5f9576c6 Fix dev 2021-06-02 10:11:29 +02:00
Brynley McDonald 1b282b65b7 Add QR code to long lived access tokens dialog (#8948)
* Add QR code to long lived access tokens dialog

* Apply suggestions from code review

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

* Further changes from code review

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-02 09:59:22 +02:00
GitHub Action e49664bad3 Translation update 2021-06-02 04:10:37 +00:00
Bram Kragten 2db9f33c41 Merge pull request #9334 from home-assistant/dev 2021-06-01 21:53:08 +02:00
Bram Kragten 2a30b55a43 Bumped version to 20210601.1 2021-06-01 21:30:51 +02:00
Paulus Schoutsen 9d0b20adce Add support for system options v2 (#9332)
* Add support for system options v2

* Update src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts

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

* Update dialog-config-entry-system-options.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-01 12:22:25 -07:00
Bram Kragten acd5e1c081 Fix back button too wide on mobile (#9333) 2021-06-01 12:12:00 -07:00
Bram Kragten cc1c5e45b2 Display error when enabling/disabling config entries (#9325) 2021-06-01 21:03:00 +02:00
Bram Kragten 038199c447 Change the type of debounce, use arrow functions (#9328) 2021-06-01 11:53:45 -07:00
Bram Kragten 8a1eab7ceb Cleanup virtualizer styles (#9327) 2021-06-01 11:51:30 -07:00
Joakim Sørensen bc5bd35448 Filter adapters with information from the Supervisor (#9322)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-01 20:12:54 +02:00
Bram Kragten 1795fd56b7 Don't rotate chart axis labels (#9329) 2021-06-01 10:28:03 -07:00
Bram Kragten 3d788d6056 Merge pull request #9324 from home-assistant/dev 2021-06-01 11:56:02 +02:00
Bram Kragten 4a7c33edad Bumped version to 20210601.0 2021-06-01 11:40:37 +02:00
rianadon 797f60d725 Show pressure units in weather details card (#9295) 2021-06-01 11:40:04 +02:00
Bram Kragten 2427d68aa1 Use local version 0.7 of lit-virtualizer (#9321) 2021-06-01 11:39:15 +02:00
GitHub Action 00c6b0f8ed Translation update 2021-06-01 04:14:44 +00:00
Paulus Schoutsen 7560f98d70 Merge pull request #9320 from home-assistant/dev 2021-05-31 15:55:09 -07:00
Paulus Schoutsen 7b8d4ab3d6 Update translations 2021-05-31 15:50:47 -07:00
Paulus Schoutsen 07a1a805f6 Bumped version to 20210531.1 2021-05-31 15:44:59 -07:00
Paulus Schoutsen d8bab6aba9 Add support for disable polling system option (#9316) 2021-05-31 15:40:50 -07:00
Bram Kragten a930e2dc75 Fix store auth disappearing (#9312) 2021-05-31 15:35:51 -07:00
J. Nick Koston 2eb35668fa Seperate addresses in network configuration (#9319) 2021-05-31 15:31:33 -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
608 changed files with 59569 additions and 31772 deletions
+51 -43
View File
@@ -35,55 +35,51 @@
"es6": true
},
"rules": {
"class-methods-use-this": 0,
"new-cap": 0,
"prefer-template": 0,
"object-shorthand": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"no-underscore-dangle": 0,
"strict": 0,
"prefer-spread": 0,
"no-plusplus": 0,
"no-bitwise": 2,
"comma-dangle": 0,
"vars-on-top": 0,
"no-continue": 0,
"no-param-reassign": 0,
"no-multi-assign": 0,
"no-console": 2,
"radix": 0,
"no-alert": 0,
"no-return-await": 0,
"no-nested-ternary": 0,
"prefer-destructuring": 0,
"class-methods-use-this": "off",
"new-cap": "off",
"prefer-template": "off",
"object-shorthand": "off",
"func-names": "off",
"no-underscore-dangle": "off",
"strict": "off",
"no-plusplus": "off",
"no-bitwise": "error",
"comma-dangle": "off",
"vars-on-top": "off",
"no-continue": "off",
"no-param-reassign": "off",
"no-multi-assign": "off",
"no-console": "error",
"radix": "off",
"no-alert": "off",
"no-nested-ternary": "off",
"prefer-destructuring": "off",
"no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": 0,
"import/order": 0,
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/no-cycle": 0,
"prefer-promise-reject-errors": "off",
"import/prefer-default-export": "off",
"import/no-default-export": "off",
"import/no-unresolved": "off",
"import/no-cycle": "off",
"import/extensions": [
2,
"error",
"ignorePackages",
{ "ts": "never", "js": "never" }
],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": 0,
"default-case": 0,
"wc/no-self-class": 0,
"no-shadow": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"object-curly-newline": "off",
"default-case": "off",
"wc/no-self-class": "off",
"no-shadow": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/naming-convention": [
0,
"off",
{
"selector": "default",
"format": ["camelCase", "snake_case"],
@@ -101,8 +97,20 @@
"format": ["PascalCase"]
}
],
"lit/attribute-value-entities": 0
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-vars": [
"error",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
],
"unused-imports/no-unused-imports": "error",
"lit/attribute-value-entities": "off"
},
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"plugins": ["disable", "unused-imports"],
"processor": "disable/disable"
}
+23 -53
View File
@@ -10,26 +10,21 @@ on:
- dev
- master
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
env:
@@ -42,51 +37,35 @@ jobs:
run: yarn run lint:types
- name: Run prettier
run: yarn run lint:prettier
- name: Check for duplicate dependencies
run: yarn dedupe --check
test:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
env:
CI: true
- name: Run Mocha
run: npm run mocha
run: yarn run mocha
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
env:
@@ -101,20 +80,11 @@ jobs:
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
env:
+9 -13
View File
@@ -4,26 +4,22 @@ on:
push:
branches:
- dev
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
env:
+13 -3
View File
@@ -6,9 +6,9 @@ on:
- published
env:
WHEELS_TAG: 3.8-alpine3.12
PYTHON_VERSION: 3.8
NODE_VERSION: 12.1
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=4096
jobs:
release:
@@ -30,7 +30,15 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download Translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Build and release package
run: |
python3 -m pip install twine
@@ -64,6 +72,8 @@ jobs:
strategy:
matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.9-alpine3.14"
steps:
- name: Download requirements.txt
uses: actions/download-artifact@v2
@@ -73,7 +83,7 @@ jobs:
- name: Build wheels
uses: home-assistant/wheels@master
with:
tag: ${{ env.WHEELS_TAG }}
tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }}
+1 -41
View File
@@ -1,8 +1,6 @@
name: Translations
on:
schedule:
- cron: "30 0 * * *"
push:
branches:
- dev
@@ -10,7 +8,7 @@ on:
- src/translations/en.json
env:
NODE_VERSION: 12
NODE_VERSION: 14
jobs:
upload:
@@ -20,46 +18,8 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Upload Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base
download:
name: Download
needs: upload
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
npm install
./script/translations_download
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
with:
name: GitHub Action
email: github-action@users.noreply.github.com
- name: Update translation
run: |
git add translations
git commit -am "Translation update"
git push
+19 -10
View File
@@ -1,10 +1,23 @@
.DS_Store
.reify-cache
# build
build
build-translations/*
node_modules/*
npm-debug.log
.DS_Store
hass_frontend/*
.reify-cache
dist
# yarn
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
node_modules/*
yarn-error.log
npm-debug.log
# Python stuff
*.py[cod]
@@ -14,11 +27,8 @@ hass_frontend/*
# venv stuff
pyvenv.cfg
pip-selfcheck.json
venv
venv/*
.venv
lib
bin
dist
# vscode
.vscode/*
@@ -31,9 +41,8 @@ src/cast/dev_const.ts
# Secrets
.lokalise_token
yarn-error.log
#asdf
# asdf
.tool-versions
# Home Assistant config
+1 -1
View File
@@ -1 +1 @@
12.1
14
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
@@ -1,14 +1,15 @@
-let _ET, ET;
+let _ET;
+let ET;
export default async function EventTarget() {
- return ET || init();
+ return ET || init();
}
async function init() {
- _ET = window.EventTarget;
- try {
- new _ET();
- }
- catch (_a) {
- _ET = (await import('event-target-shim')).EventTarget;
- }
- return (ET = _ET);
+ _ET = window.EventTarget;
+ try {
+ new _ET();
+ } catch (_a) {
+ _ET = (await import("event-target-shim")).default.EventTarget;
+ }
+ return (ET = _ET);
}
@@ -0,0 +1,34 @@
diff --git a/lib/legacy/class.js b/lib/legacy/class.js
index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644
--- a/lib/legacy/class.js
+++ b/lib/legacy/class.js
@@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) {
// only proceed if the generated class' prototype has not been registered.
const generatedProto = PolymerGenerated.prototype;
if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) {
- generatedProto.__hasRegisterFinished = true;
+ // make sure legacy lifecycle is called on the *element*'s prototype
+ // and not the generated class prototype; if the element has been
+ // extended, these are *not* the same.
+ const proto = Object.getPrototypeOf(this);
+ // Only set flag when generated prototype itself is registered,
+ // as this element may be extended from, and needs to run `registered`
+ // on all behaviors on the subclass as well.
+ if (proto === generatedProto) {
+ generatedProto.__hasRegisterFinished = true;
+ }
// ensure superclass is registered first.
super._registered();
// copy properties onto the generated class lazily if we're optimizing,
- if (legacyOptimizations) {
+ if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) {
+ generatedProto.__hasCopiedProperties = true;
copyPropertiesToProto(generatedProto);
}
- // make sure legacy lifecycle is called on the *element*'s prototype
- // and not the generated class prototype; if the element has been
- // extended, these are *not* the same.
- const proto = Object.getPrototypeOf(this);
let list = lifecycle.beforeRegister;
if (list) {
for (let i=0; i < list.length; i++) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+55
View File
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-2.4.2.cjs
@@ -0,0 +1,170 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
module.exports = function inlineConstants(babel, options, cwd) {
const t = babel.types;
if (!Array.isArray(options.modules)) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `modules` array to be passed"
);
}
if (options.resolveExtensions && !Array.isArray(options.resolveExtensions)) {
throw new TypeError(
"babel-plugin-inline-constants: expected `resolveExtensions` to be an array"
);
}
const ignoreModuleNotFound = options.ignoreModuleNotFound;
const resolveExtensions = options.resolveExtensions;
const hasRelativeModules = options.modules.some(
(module) => module.startsWith(".") || module.startsWith("/")
);
const modules = Object.fromEntries(
options.modules.map((module) => {
const absolute = module.startsWith(".")
? require.resolve(module, { paths: [cwd] })
: module;
// eslint-disable-next-line import/no-dynamic-require
return [absolute, require(absolute)];
})
);
const toLiteral = (value) => {
if (typeof value === "string") {
return t.stringLiteral(value);
}
if (typeof value === "number") {
return t.numericLiteral(value);
}
if (typeof value === "boolean") {
return t.booleanLiteral(value);
}
if (value === null) {
return t.nullLiteral();
}
throw new Error(
"babel-plugin-inline-constants: cannot handle non-literal `" + value + "`"
);
};
const resolveAbsolute = (value, state, resolveExtensionIndex) => {
if (!state.filename) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `filename` to be set for files"
);
}
if (resolveExtensions && resolveExtensionIndex !== undefined) {
value += resolveExtensions[resolveExtensionIndex];
}
try {
return require.resolve(value, { paths: [path.dirname(state.filename)] });
} catch (error) {
if (
error.code === "MODULE_NOT_FOUND" &&
resolveExtensions &&
(resolveExtensionIndex === undefined ||
resolveExtensionIndex < resolveExtensions.length - 1)
) {
const resolveExtensionIdx = (resolveExtensionIndex || -1) + 1;
return resolveAbsolute(value, state, resolveExtensionIdx);
}
if (error.code === "MODULE_NOT_FOUND" && ignoreModuleNotFound) {
return undefined;
}
throw error;
}
};
const importDeclaration = (p, state) => {
if (p.node.type !== "ImportDeclaration") {
return;
}
const absolute =
hasRelativeModules && p.node.source.value.startsWith(".")
? resolveAbsolute(p.node.source.value, state)
: p.node.source.value;
if (!absolute || !(absolute in modules)) {
return;
}
const module = modules[absolute];
for (const specifier of p.node.specifiers) {
if (
specifier.type === "ImportDefaultSpecifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!("default" in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access default export from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module.default);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else if (
specifier.type === "ImportSpecifier" &&
specifier.imported &&
specifier.imported.type === "Identifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!(specifier.imported.name in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access `" +
specifier.imported.name +
"` from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module[specifier.imported.name]);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else {
throw new Error("Cannot handle specifier `" + specifier.type + "`");
}
}
p.remove();
};
return {
visitor: {
ImportDeclaration: importDeclaration,
},
};
};
+21 -11
View File
@@ -5,8 +5,6 @@ const paths = require("./paths.js");
// Files from NPM Packages that should not be imported
module.exports.ignorePackages = ({ latestBuild }) => [
// Bloats bundle and it's not used.
path.resolve(require.resolve("moment"), "../locale"),
// Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"),
];
@@ -20,7 +18,8 @@ module.exports.emptyPackages = ({ latestBuild }) =>
require.resolve("@polymer/paper-styles/default-theme.js"),
// Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
// Compatibility not needed for latest builds
latestBuild &&
// wrapped in require.resolve so it blows up if file no longer exists
@@ -52,17 +51,29 @@ module.exports.terserOptions = (latestBuild) => ({
module.exports.babelOptions = ({ latestBuild }) => ({
babelrc: false,
compact: false,
presets: [
!latestBuild && [
"@babel/preset-env",
{
useBuiltIns: "entry",
corejs: "3.6",
corejs: "3.15",
bugfixes: true,
},
],
"@babel/preset-typescript",
].filter(Boolean),
plugins: [
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/inline-constants-plugin.js"
),
{
modules: ["@mdi/js"],
ignoreModuleNotFound: true,
},
],
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [
"@babel/plugin-proposal-object-rest-spread",
@@ -75,17 +86,16 @@ module.exports.babelOptions = ({ latestBuild }) => ({
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
].filter(Boolean),
exclude: [
// \\ for Windows, / for Mac OS and Linux
/node_modules[\\/]core-js/,
/node_modules[\\/]webpack[\\/]buildin/,
],
});
// Are already ES5, cause warnings when babelified.
module.exports.babelExclude = () => [
require.resolve("@mdi/js/mdi.js"),
require.resolve("hls.js"),
require.resolve("vis-network/peer/esm/vis-network.js"),
];
const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
+2 -2
View File
@@ -47,8 +47,8 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
...// Don't compress running tests
(env.isTest() ? [] : ["compress-app"]),
// Don't compress running tests
...(env.isTest() ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",
+15 -15
View File
@@ -5,32 +5,32 @@ require("./translations");
gulp.task(
"clean",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([paths.app_output_root, paths.build_dir]);
})
gulp.parallel("clean-translations", () =>
del([paths.app_output_root, paths.build_dir])
)
);
gulp.task(
"clean-demo",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([paths.demo_output_root, paths.build_dir]);
})
gulp.parallel("clean-translations", () =>
del([paths.demo_output_root, paths.build_dir])
)
);
gulp.task(
"clean-cast",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([paths.cast_output_root, paths.build_dir]);
})
gulp.parallel("clean-translations", () =>
del([paths.cast_output_root, paths.build_dir])
)
);
gulp.task("clean-hassio", function cleanOutputAndBuildDir() {
return del([paths.hassio_output_root, paths.build_dir]);
});
gulp.task("clean-hassio", () =>
del([paths.hassio_output_root, paths.build_dir])
);
gulp.task(
"clean-gallery",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([paths.gallery_output_root, paths.build_dir]);
})
gulp.parallel("clean-translations", () =>
del([paths.gallery_output_root, paths.build_dir])
)
);
+11 -3
View File
@@ -302,15 +302,23 @@ gulp.task("gen-index-hassio-prod", async () => {
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
function loadES5() {
var el = document.createElement('script');
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
loadES5();
} else {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
loadES5();
}
}
`,
{ encoding: "utf-8" }
+10 -6
View File
@@ -2,7 +2,6 @@
const gulp = require("gulp");
const path = require("path");
const cpx = require("cpx");
const fs = require("fs-extra");
const paths = require("../paths");
@@ -13,8 +12,10 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath = (staticDir) => (...parts) =>
path.resolve(staticDir, ...parts);
const genStaticPath =
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) {
const staticPath = genStaticPath(staticDir);
@@ -62,9 +63,12 @@ function copyLoaderJS(staticDir) {
function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir);
// Local fonts
cpx.copySync(
npmPath("roboto-fontface/fonts/roboto/*.woff2"),
staticPath("fonts/roboto")
fs.copySync(
npmPath("roboto-fontface/fonts/roboto/"),
staticPath("fonts/roboto/"),
{
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
}
);
}
+98 -101
View File
@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const crypto = require("crypto");
const del = require("del");
const path = require("path");
@@ -26,13 +28,6 @@ gulp.task("translations-enable-merge-backend", (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.
const TRANSLATION_FRAGMENTS = Object.keys(
require("../../src/translations/en.json").ui.panel
@@ -40,7 +35,7 @@ const TRANSLATION_FRAGMENTS = Object.keys(
function recursiveFlatten(prefix, data) {
let output = {};
Object.keys(data).forEach(function (key) {
Object.keys(data).forEach((key) => {
if (typeof data[key] === "object") {
output = {
...output,
@@ -101,15 +96,19 @@ function lokaliseTransform(data, original, file) {
if (value instanceof Object) {
output[key] = lokaliseTransform(value, original, file);
} else {
output[key] = value.replace(re_key_reference, (match, key) => {
const replace = key.split("::").reduce((tr, k) => {
output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
const replace = lokalise_key.split("::").reduce((tr, k) => {
if (!tr) {
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
}
return tr[k];
}, original);
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;
});
@@ -118,9 +117,7 @@ function lokaliseTransform(data, original, file) {
return output;
}
gulp.task("clean-translations", function () {
return del([workDir]);
});
gulp.task("clean-translations", () => del([workDir]));
gulp.task("ensure-translations-build-dir", (done) => {
if (!fs.existsSync(workDir)) {
@@ -129,7 +126,7 @@ gulp.task("ensure-translations-build-dir", (done) => {
done();
});
gulp.task("create-test-metadata", function (cb) {
gulp.task("create-test-metadata", (cb) => {
fs.writeFile(
workDir + "/testMetadata.json",
JSON.stringify({
@@ -143,17 +140,13 @@ gulp.task("create-test-metadata", function (cb) {
gulp.task(
"create-test-translation",
gulp.series("create-test-metadata", function createTestTranslation() {
return gulp
gulp.series("create-test-metadata", () =>
gulp
.src(path.join(paths.translations_src, "en.json"))
.pipe(
transform(function (data, file) {
return recursiveEmpty(data);
})
)
.pipe(transform((data, _file) => recursiveEmpty(data)))
.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
* 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")];
if (mergeBackend) {
@@ -174,11 +167,7 @@ gulp.task("build-master-translation", function () {
return gulp
.src(src)
.pipe(
transform(function (data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
merge({
fileName: "translationMaster.json",
@@ -187,18 +176,14 @@ gulp.task("build-master-translation", function () {
.pipe(gulp.dest(workDir));
});
gulp.task("build-merged-translations", function () {
return gulp
gulp.task("build-merged-translations", () =>
gulp
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
allowEmpty: true,
})
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
transform(function (data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(
foreach(function (stream, file) {
foreach((stream, file) => {
// For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag
@@ -230,17 +215,17 @@ gulp.task("build-merged-translations", function () {
)
.pipe(gulp.dest(fullDir));
})
);
});
)
);
var taskName;
let taskName;
const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = "build-translation-fragment-" + fragment;
gulp.task(taskName, function () {
gulp.task(taskName, () =>
// Return only the translations for this fragment.
return gulp
gulp
.src(fullDir + "/*.json")
.pipe(
transform((data) => ({
@@ -251,18 +236,18 @@ TRANSLATION_FRAGMENTS.forEach((fragment) => {
},
}))
)
.pipe(gulp.dest(workDir + "/" + fragment));
});
.pipe(gulp.dest(workDir + "/" + fragment))
);
splitTasks.push(taskName);
});
taskName = "build-translation-core";
gulp.task(taskName, function () {
gulp.task(taskName, () =>
// Remove the fragment translations from the core translation.
return gulp
gulp
.src(fullDir + "/*.json")
.pipe(
transform((data, file) => {
transform((data, _file) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
@@ -270,14 +255,14 @@ gulp.task(taskName, function () {
return data;
})
)
.pipe(gulp.dest(coreDir));
});
.pipe(gulp.dest(coreDir))
);
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
return gulp
gulp
.src(
TRANSLATION_FRAGMENTS.map(
(fragment) => workDir + "/" + fragment + "/*.json"
@@ -285,41 +270,45 @@ gulp.task("build-flattened-translations", function () {
{ base: workDir }
)
.pipe(
transform(function (data) {
transform((data) =>
// Polymer.AppLocalizeBehavior requires flattened json
return flatten(data);
})
flatten(data)
)
)
.pipe(
rename((filePath) => {
if (filePath.dirname === "core") {
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 = {};
gulp.task(
"build-translation-fingerprints",
function fingerprintTranslationFiles() {
// Fingerprint full file of each language
const files = fs.readdirSync(fullDir);
gulp.task("build-translation-fingerprints", () => {
// Fingerprint full file of each language
const files = fs.readdirSync(fullDir);
for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes
hash: env.isProdBuild()
? crypto
.createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex")
: "dev",
};
}
for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes
hash: env.isProdBuild()
? crypto
.createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex")
: "dev",
};
}
// In dev we create the file with the fake hash in the filename
if (env.isProdBuild()) {
mapFiles(outDir, ".json", (filename) => {
const parsed = path.parse(filename);
@@ -335,35 +324,35 @@ 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 () {
return gulp
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", () =>
gulp
.src(fullDir + "/*.json")
.pipe(transform((data) => data.supervisor))
.pipe(gulp.dest(workDir + "/supervisor"));
});
.pipe(gulp.dest(workDir + "/supervisor"))
);
gulp.task("build-translation-flatten-supervisor", function () {
return gulp
gulp.task("build-translation-flatten-supervisor", () =>
gulp
.src(workDir + "/supervisor/*.json")
.pipe(
transform(function (data) {
transform((data) =>
// Polymer.AppLocalizeBehavior requires flattened json
return flatten(data);
})
flatten(data)
)
)
.pipe(gulp.dest(outDir));
});
.pipe(gulp.dest(outDir))
);
gulp.task("build-translation-write-metadata", function writeMetadata() {
return gulp
gulp.task("build-translation-write-metadata", () =>
gulp
.src(
[
path.join(paths.translations_src, "translationMetadata.json"),
@@ -374,13 +363,14 @@ gulp.task("build-translation-write-metadata", function writeMetadata() {
)
.pipe(merge({}))
.pipe(
transform(function (data) {
transform((data) => {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (value.nativeName) {
newData[key] = value;
} else {
// eslint-disable-next-line no-console
console.warn(
`Skipping language ${key}. Native name was not translated.`
);
@@ -396,19 +386,26 @@ gulp.task("build-translation-write-metadata", function writeMetadata() {
}))
)
.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(
"build-translations",
gulp.series(
"clean-translations",
"ensure-translations-build-dir",
env.isProdBuild() ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations",
"create-translations",
"build-translation-fingerprints",
"build-translation-write-metadata"
)
+18 -7
View File
@@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Tasks to run webpack.
const fs = require("fs");
const gulp = require("gulp");
const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
@@ -18,6 +20,13 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }),
];
const isWsl =
fs.existsSync("/proc/version") &&
fs
.readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase()
.includes("microsoft");
/**
* @param {{
* compiler: import("webpack").Compiler,
@@ -36,7 +45,7 @@ const runDevServer = ({
open: true,
watchContentBase: true,
contentBase,
}).listen(port, listenHost, function (err) {
}).listen(port, listenHost, (err) => {
if (err) {
throw err;
}
@@ -57,6 +66,7 @@ const doneHandler = (done) => (err, stats) => {
}
if (stats.hasErrors() || stats.hasWarnings()) {
// eslint-disable-next-line no-console
console.log(stats.toString("minimal"));
}
@@ -78,13 +88,14 @@ const prodBuild = (conf) =>
gulp.task("webpack-watch-app", () => {
// This command will run forever because we don't close compiler
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
{ ignored: /build-translations/ },
doneHandler()
);
webpack(
process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("build-translations", "copy-translations-app")
gulp.series("create-translations", "copy-translations-app")
);
});
@@ -137,7 +148,7 @@ gulp.task("webpack-watch-hassio", () => {
isProdBuild: false,
latestBuild: true,
})
).watch({ ignored: /build-translations/ }, doneHandler());
).watch({ ignored: /build-translations/, poll: isWsl }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),
File diff suppressed because one or more lines are too long
-1
View File
@@ -57,7 +57,6 @@ const createRollupConfig = ({
babel({
...bundle.babelOptions({ latestBuild }),
extensions,
exclude: bundle.babelExclude(),
babelHelpers: isWDS ? "inline" : "bundled",
}),
string({
+10 -16
View File
@@ -47,15 +47,18 @@ const createWebpackConfig = ({
rules: [
{
test: /\.m?js$|\.ts$/,
exclude: bundle.babelExclude(),
use: {
loader: "babel-loader",
options: bundle.babelOptions({ latestBuild }),
options: {
...bundle.babelOptions({ latestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
},
},
{
test: /\.css$/,
use: "raw-loader",
type: "asset/source",
},
],
},
@@ -67,6 +70,8 @@ const createWebpackConfig = ({
terserOptions: bundle.terserOptions(latestBuild),
}),
],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
new WebpackManifestPlugin({
@@ -113,15 +118,6 @@ const createWebpackConfig = ({
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
new webpack.NormalModuleReplacementPlugin(
new RegExp(
require.resolve(
"@lit-labs/virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
)
),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
),
!isProdBuild && new LogStartCompilePlugin(),
].filter(Boolean),
resolve: {
@@ -134,15 +130,13 @@ const createWebpackConfig = ({
},
output: {
filename: ({ chunk }) => {
if (!isProdBuild || dontHash.has(chunk.name)) {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
},
chunkFilename:
isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js"
: "[name].chunk.js",
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
path: outputPath,
publicPath,
// To silence warning in worker plugin
+1 -1
View File
@@ -139,7 +139,7 @@
Your authentication credentials or Home Assistant url are never sent
to the Cloud. You can validate this behavior in
<a
href="https://github.com/home-assistant/home-assistant-polymer/tree/dev/cast"
href="https://github.com/home-assistant/frontend/tree/dev/cast"
target="_blank"
>the source code</a
>.
+2 -2
View File
@@ -5,8 +5,8 @@ import {
import { castContext } from "../cast_context";
export const castDemoLovelace: () => LovelaceConfig = () => {
const touchSupported = castContext.getDeviceCapabilities()
.touch_input_supported;
const touchSupported =
castContext.getDeviceCapabilities().touch_input_supported;
return {
views: [
{
+9 -8
View File
@@ -29,6 +29,11 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
},
],
},
{
title: "Energy distribution today",
type: "energy-distribution",
link_dashboard: true,
},
{
type: "thermostat",
entity: "climate.upstairs",
@@ -113,8 +118,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -196,8 +200,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -277,8 +280,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -315,8 +317,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
+4 -3
View File
@@ -1,5 +1,6 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace } from "../../../src/panels/lovelace/types";
import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
@@ -12,9 +13,8 @@ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
// eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfigIndex = 0;
// eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[
selectedDemoConfigIndex
]();
export let selectedDemoConfig: Promise<DemoConfig> =
demoConfigs[selectedDemoConfigIndex]();
export const setDemoConfig = async (
hass: MockHomeAssistant,
@@ -28,6 +28,7 @@ export const setDemoConfig = async (
selectedDemoConfig = confProm;
hass.addEntities(config.entities(hass.localize), true);
hass.addEntities(energyEntities());
lovelace.saveConfig(config.lovelace(hass.localize));
hass.mockTheme(config.theme());
};
+2 -4
View File
@@ -980,8 +980,7 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-off",
custom_ui_state_card: "state-card-custom-ui",
templates: {
icon:
"if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
icon: "if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
},
@@ -1005,8 +1004,7 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-multiple-minus",
custom_ui_state_card: "state-card-custom-ui",
templates: {
icon:
"if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
icon: "if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
},
+2 -7
View File
@@ -19,7 +19,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: MockHomeAssistant;
@state() private _switching?: boolean;
@state() private _switching = false;
private _hidden = localStorage.hide_demo_card;
@@ -27,12 +27,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
return this._hidden ? 0 : 2;
}
public setConfig(
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
config: LovelaceCardConfig
// eslint-disable-next-line @typescript-eslint/no-empty-function
) {}
public setConfig(_config: LovelaceCardConfig) {}
protected render(): TemplateResult {
if (this._hidden) {
-2
View File
@@ -1,5 +1,3 @@
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style";
import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch";
+7
View File
@@ -20,6 +20,9 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
@@ -47,8 +50,12 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass);
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass);
hass.addEntities(energyEntities());
// Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then(
([conf, localize]) => {
+41
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
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
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",
},
},
});
+249 -9
View File
@@ -1,4 +1,6 @@
import { addHours, differenceInHours, endOfDay } from "date-fns";
import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams {
@@ -64,17 +66,219 @@ const generateHistory = (state, deltas) => {
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) => {
mockHass.mockAPI(
new RegExp("history/period/.+"),
(
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
(hass, _method, path, _parameters) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
@@ -95,7 +299,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
const numberState = Number(state.state);
if (isNaN(numberState)) {
// eslint-disable-next-line
// eslint-disable-next-line no-console
console.log(
"Ignoring state with unparsable state but with a unit",
entityId,
@@ -140,4 +344,40 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
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;
}
);
};
+3 -4
View File
@@ -10,10 +10,9 @@ export const mockLovelace = (
localizePromise: Promise<LocalizeFunc>
) => {
hass.mockWS("lovelace/config", () =>
Promise.all([
selectedDemoConfig,
localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
Promise.all([selectedDemoConfig, localizePromise]).then(
([config, localize]) => config.lovelace(localize)
)
);
hass.mockWS("lovelace/config/save", () => Promise.resolve());
+1 -1
View File
@@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
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!({
result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false },
+1 -1
View File
@@ -2,12 +2,12 @@ import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph";
import "../../../src/components/trace/hat-trace-timeline";
import { customElement, property, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../src/types";
import { DemoTrace } from "../data/traces/types";
import { basicTrace } from "../data/traces/basic_trace";
import { motionLightTrace } from "../data/traces/motion-light-trace";
import { customElement, property, state } from "lit/decorators";
const traces: DemoTrace[] = [basicTrace, motionLightTrace];
+150
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;
}
}
+8 -3
View File
@@ -2,6 +2,8 @@ import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
import { IntegrationManifest } from "../../../src/data/integration";
import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -15,8 +17,6 @@ import type {
} from "../../../src/panels/config/integrations/ha-config-integrations";
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
const createConfigEntry = (
title: string,
@@ -28,10 +28,11 @@ const createConfigEntry = (
title,
source: "zeroconf",
state: "loaded",
connection_class: "local_push",
supports_options: false,
supports_unload: true,
disabled_by: null,
pref_disable_new_entities: false,
pref_disable_polling: false,
reason: null,
...override,
});
@@ -64,6 +65,9 @@ const configPanelEntry = createConfigEntry("Config Panel", {
const optionsFlowEntry = createConfigEntry("Options Flow", {
supports_options: true,
});
const disabledPollingEntry = createConfigEntry("Disabled Polling", {
pref_disable_polling: true,
});
const setupErrorEntry = createConfigEntry("Setup Error", {
state: "setup_error",
});
@@ -136,6 +140,7 @@ const configEntries: Array<{
{ items: [loadedEntry] },
{ items: [configPanelEntry] },
{ items: [optionsFlowEntry] },
{ items: [disabledPollingEntry] },
{ items: [nameAsDomainEntry] },
{ items: [longNameEntry] },
{ items: [longNonBreakingNameEntry] },
+8
View File
@@ -172,6 +172,14 @@ class HaGallery extends PolymerElement {
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) => {
if (ev.detail.entityId) {
this.$.notifications.showDialog({
@@ -13,6 +13,7 @@ import {
import { customElement, property, state } from "lit/decorators";
import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import {
HassioAddonDetails,
@@ -53,7 +54,9 @@ class HassioAddonAudio extends LitElement {
.header=${this.supervisor.localize("addon.configuration.audio.header")}
>
<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
.label=${this.supervisor.localize(
@@ -117,10 +120,6 @@ class HassioAddonAudio extends LitElement {
paper-dropdown-menu {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
paper-item {
width: 450px;
}
@@ -2,7 +2,6 @@ import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import { DEFAULT_SCHEMA, Type } from "js-yaml";
import {
css,
@@ -18,6 +17,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/ha-form";
import "../../../../src/components/ha-formfield";
@@ -134,19 +134,21 @@ class HassioAddonConfig extends LitElement {
></ha-form>`
: html` <ha-yaml-editor
@value-changed=${this._configChanged}
.schema=${ADDON_YAML_SCHEMA}
.yamlSchema=${ADDON_YAML_SCHEMA}
></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._canShowSchema && this.addon.schema) ||
this._valid
? ""
: html`
<div class="errors">
<ha-alert alert-type="error">
${this.supervisor.localize(
"addon.configuration.options.invalid_yaml"
)}
</div>
</ha-alert>
`}
</div>
${hasHiddenOptions
@@ -269,6 +271,9 @@ class HassioAddonConfig extends LitElement {
private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
const options: Record<string, unknown> = this._yamlMode
? this._editor?.value
: this._options;
const eventdata = {
success: true,
response: undefined,
@@ -282,13 +287,13 @@ class HassioAddonConfig extends LitElement {
const validation = await validateHassioAddonOption(
this.hass,
this.addon.slug,
this._editor?.value
options
);
if (!validation.valid) {
throw Error(validation.message);
}
await setHassioAddonOption(this.hass, this.addon.slug, {
options: this._yamlMode ? this._editor?.value : this._options,
options,
});
this._configHasChanged = false;
@@ -322,17 +327,7 @@ class HassioAddonConfig extends LitElement {
display: flex;
justify-content: space-between;
}
.errors {
color: var(--error-color);
margin-top: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: var(--code-font-family, monospace);
}
.syntaxerror {
color: var(--error-color);
}
.card-menu {
float: right;
z-index: 3;
@@ -10,6 +10,7 @@ import {
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import {
HassioAddonDetails,
@@ -62,7 +63,9 @@ class HassioAddonNetwork extends LitElement {
)}
>
<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>
<tbody>
@@ -225,10 +228,6 @@ class HassioAddonNetwork extends LitElement {
ha-card {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.card-actions {
display: flex;
justify-content: space-between;
@@ -1,7 +1,9 @@
import "../../../../src/components/ha-card";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import { customElement, property, state } from "lit/decorators";
import {
fetchHassioAddonDocumentation,
HassioAddonDetails,
@@ -12,7 +14,6 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
@customElement("hassio-addon-documentation-tab")
class HassioAddonDocumentationDashboard extends LitElement {
@@ -38,7 +39,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
return html`
<div class="content">
<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">
${this._content
? html`<ha-markdown .content=${this._content}></ha-markdown>`
+29 -16
View File
@@ -23,6 +23,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown";
@@ -143,14 +144,14 @@ class HassioAddonInfo extends LitElement {
this.addon.arch
)
? html`
<p class="warning">
<ha-alert alert-type="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch"
)}
</p>
</ha-alert>
`
: html`
<p class="warning">
<ha-alert alert-type="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch",
"core_version_installed",
@@ -158,7 +159,7 @@ class HassioAddonInfo extends LitElement {
"core_version_needed",
addonStoreInfo.homeassistant
)}
</p>
</ha-alert>
`
: ""}
</div>
@@ -569,21 +570,23 @@ class HassioAddonInfo extends LitElement {
: ""}
</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
? !addonArchIsSupported(
this.supervisor.info.supported_arch,
this.addon.arch
)
? html`
<p class="warning">
<ha-alert alert-type="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch"
)}
</p>
</ha-alert>
`
: html`
<p class="warning">
<ha-alert alert-type="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_version",
"core_version_installed",
@@ -591,7 +594,7 @@ class HassioAddonInfo extends LitElement {
"core_version_needed",
addonStoreInfo!.homeassistant
)}
</p>
</ha-alert>
`
: ""}
</div>
@@ -892,10 +895,19 @@ class HassioAddonInfo extends LitElement {
private async _openChangelog(): Promise<void> {
try {
const content = await fetchHassioAddonChangelog(
this.hass,
this.addon.slug
);
let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug);
if (
content.includes(`# ${this.addon.version}`) &&
content.includes(`# ${this.addon.version_latest}`)
) {
const newcontent = content.split(`# ${this.addon.version}`)[0];
if (newcontent.includes(`# ${this.addon.version_latest}`)) {
// Only change the content if the new version still exist
// if the changelog does not have the newests version on top
// this will not be true, and we don't modify the content
content = newcontent;
}
}
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"),
content,
@@ -978,7 +990,7 @@ class HassioAddonInfo extends LitElement {
supervisor: this.supervisor,
name: this.addon.name,
version: this.addon.version_latest,
snapshotParams: {
backupParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug],
homeassistant: false,
@@ -1140,6 +1152,7 @@ class HassioAddonInfo extends LitElement {
margin-bottom: 16px;
}
img.logo {
max-width: 100%;
max-height: 60px;
margin: 16px 0;
display: block;
@@ -1149,10 +1162,10 @@ class HassioAddonInfo extends LitElement {
display: flex;
}
ha-svg-icon.running {
color: var(--paper-green-400);
color: var(--success-color);
}
ha-svg-icon.stopped {
color: var(--google-red-300);
color: var(--error-color);
}
ha-call-api-button {
font-weight: 500;
@@ -1,6 +1,7 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import {
fetchHassioAddonLogs,
@@ -34,7 +35,9 @@ class HassioAddonLogs extends LitElement {
return html`
<h1>${this.addon.name}</h1>
<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">
${this._content
? html`<hassio-ansi-to-html
@@ -60,10 +63,6 @@ class HassioAddonLogs extends LitElement {
ha-card {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
`,
];
}
@@ -25,12 +25,12 @@ import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
fetchHassioSnapshots,
fetchHassioBackups,
friendlyFolderName,
HassioSnapshot,
reloadHassioSnapshots,
removeSnapshot,
} from "../../../src/data/hassio/snapshot";
HassioBackup,
reloadHassioBackups,
removeBackup,
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
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 { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload";
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-snapshots")
export class HassioSnapshots extends LitElement {
@customElement("hassio-backups")
export class HassioBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -58,9 +58,9 @@ export class HassioSnapshots extends LitElement {
@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)
private _dataTable!: HaTabsSubpageDataTable;
@@ -75,26 +75,26 @@ export class HassioSnapshots extends LitElement {
}
public async refreshData() {
await reloadHassioSnapshots(this.hass);
await this.fetchSnapshots();
await reloadHassioBackups(this.hass);
await this.fetchBackups();
}
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => {
if (snapshot.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot");
private _computeBackupContent = (backup: HassioBackup): string => {
if (backup.type === "full") {
return this.supervisor.localize("backup.full_backup");
}
const content: string[] = [];
if (snapshot.content.homeassistant) {
if (backup.content.homeassistant) {
content.push("Home Assistant");
}
if (snapshot.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) {
if (backup.content.folders.length !== 0) {
for (const folder of backup.content.folders) {
content.push(friendlyFolderName[folder] || folder);
}
}
if (snapshot.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) {
if (backup.content.addons.length !== 0) {
for (const addon of backup.content.addons) {
content.push(
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
@@ -117,16 +117,16 @@ export class HassioSnapshots extends LitElement {
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor?.localize("snapshot.name") || "",
title: this.supervisor?.localize("backup.name") || "",
sortable: true,
filterable: true,
grows: true,
template: (entry: string, snapshot: any) =>
html`${entry || snapshot.slug}
<div class="secondary">${snapshot.secondary}</div>`,
template: (entry: string, backup: any) =>
html`${entry || backup.slug}
<div class="secondary">${backup.secondary}</div>`,
},
date: {
title: this.supervisor?.localize("snapshot.created") || "",
title: this.supervisor?.localize("backup.created") || "",
width: "15%",
direction: "desc",
hidden: narrow,
@@ -143,10 +143,10 @@ export class HassioSnapshots extends LitElement {
})
);
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) =>
snapshots.map((snapshot) => ({
...snapshot,
secondary: this._computeSnapshotContent(snapshot),
private _backupData = memoizeOne((backups: HassioBackup[]) =>
backups.map((backup) => ({
...backup,
secondary: this._computeBackupContent(backup),
}))
);
@@ -160,11 +160,11 @@ export class HassioSnapshots extends LitElement {
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")}
.noDataText=${this.supervisor.localize("backup.no_backups")}
.narrow=${this.narrow}
.route=${this.route}
.columns=${this._columns(this.narrow)}
.data=${this._snapshotData(this._snapshots || [])}
.data=${this._backupData(this._backups || [])}
id="slug"
@row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged}
@@ -187,12 +187,12 @@ export class HassioSnapshots extends LitElement {
</mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item>
${this.supervisor?.localize("snapshot.upload_snapshot")}
${this.supervisor?.localize("backup.upload_backup")}
</mwc-list-item>`
: ""}
</ha-button-menu>
${this._selectedSnapshots.length
${this._selectedBackups.length
? html`<div
class=${classMap({
"header-toolbar": this.narrow,
@@ -201,8 +201,8 @@ export class HassioSnapshots extends LitElement {
slot="header"
>
<p class="selected-txt">
${this.supervisor.localize("snapshot.selected", {
number: this._selectedSnapshots.length,
${this.supervisor.localize("backup.selected", {
number: this._selectedBackups.length,
})}
</p>
<div class="header-btns">
@@ -212,7 +212,7 @@ export class HassioSnapshots extends LitElement {
@click=${this._deleteSelected}
class="warning"
>
${this.supervisor.localize("snapshot.delete_selected")}
${this.supervisor.localize("backup.delete_selected")}
</mwc-button>
`
: html`
@@ -224,7 +224,7 @@ export class HassioSnapshots extends LitElement {
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("snapshot.delete_selected")}
${this.supervisor.localize("backup.delete_selected")}
</paper-tooltip>
`}
</div>
@@ -233,8 +233,8 @@ export class HassioSnapshots extends LitElement {
<ha-fab
slot="fab"
@click=${this._createSnapshot}
.label=${this.supervisor.localize("snapshot.create_snapshot")}
@click=${this._createBackup}
.label=${this.supervisor.localize("backup.create_backup")}
extended
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
@@ -249,7 +249,7 @@ export class HassioSnapshots extends LitElement {
this.refreshData();
break;
case 1:
this._showUploadSnapshotDialog();
this._showUploadBackupDialog();
break;
}
}
@@ -257,33 +257,33 @@ export class HassioSnapshots extends LitElement {
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedSnapshots = ev.detail.value;
this._selectedBackups = ev.detail.value;
}
private _showUploadSnapshotDialog() {
showSnapshotUploadDialog(this, {
showSnapshot: (slug: string) =>
showHassioSnapshotDialog(this, {
private _showUploadBackupDialog() {
showBackupUploadDialog(this, {
showBackup: (slug: string) =>
showHassioBackupDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(),
onDelete: () => this.fetchBackups(),
}),
reloadSnapshot: () => this.refreshData(),
reloadBackup: () => this.refreshData(),
});
}
private async fetchSnapshots() {
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
private async fetchBackups() {
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
}
private async _deleteSelected() {
const confirm = await showConfirmationDialog(this, {
title: this.supervisor.localize("snapshot.delete_snapshot_title"),
text: this.supervisor.localize("snapshot.delete_snapshot_text", {
number: this._selectedSnapshots.length,
title: this.supervisor.localize("backup.delete_backup_title"),
text: this.supervisor.localize("backup.delete_backup_text", {
number: this._selectedBackups.length,
}),
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
});
if (!confirm) {
@@ -292,44 +292,44 @@ export class HassioSnapshots extends LitElement {
try {
await Promise.all(
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("snapshot.failed_to_delete"),
title: this.supervisor.localize("backup.failed_to_delete"),
text: extractApiErrorMessage(err),
});
return;
}
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
this._dataTable.clearSelection();
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id;
showHassioSnapshotDialog(this, {
showHassioBackupDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(),
onDelete: () => this.fetchBackups(),
});
}
private _createSnapshot() {
private _createBackup() {
if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, {
title: this.supervisor!.localize("snapshot.could_not_create"),
title: this.supervisor!.localize("backup.could_not_create"),
text: this.supervisor!.localize(
"snapshot.create_blocked_not_running",
"backup.create_blocked_not_running",
"state",
this.supervisor!.info.state
),
});
return;
}
showHassioCreateSnapshotDialog(this, {
showHassioCreateBackupDialog(this, {
supervisor: this.supervisor!,
onCreate: () => this.fetchSnapshots(),
onCreate: () => this.fetchBackups(),
});
}
@@ -378,6 +378,6 @@ export class HassioSnapshots extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"hassio-snapshots": HassioSnapshots;
"hassio-backups": HassioBackups;
}
}
+8 -8
View File
@@ -41,16 +41,16 @@ class HassioAnsiToHtml extends LitElement {
text-decoration: underline line-through;
}
.fg-red {
color: rgb(222, 56, 43);
color: var(--error-color);
}
.fg-green {
color: rgb(57, 181, 74);
color: var(--success-color);
}
.fg-yellow {
color: rgb(255, 199, 6);
color: var(--warning-color);
}
.fg-blue {
color: rgb(0, 111, 184);
color: var(--info-color);
}
.fg-magenta {
color: rgb(118, 38, 113);
@@ -65,16 +65,16 @@ class HassioAnsiToHtml extends LitElement {
background-color: rgb(0, 0, 0);
}
.bg-red {
background-color: rgb(222, 56, 43);
background-color: var(--error-color);
}
.bg-green {
background-color: rgb(57, 181, 74);
background-color: var(--success-color);
}
.bg-yellow {
background-color: rgb(255, 199, 6);
background-color: var(--warning-color);
}
.bg-blue {
background-color: rgb(0, 111, 184);
background-color: var(--info-color);
}
.bg-magenta {
background-color: rgb(118, 38, 113);
+4 -4
View File
@@ -80,14 +80,14 @@ class HassioCardContent extends LitElement {
color: var(--secondary-text-color);
}
ha-svg-icon.update {
color: var(--paper-orange-400);
color: var(--warning-color);
}
ha-svg-icon.running,
ha-svg-icon.installed {
color: var(--paper-green-400);
color: var(--success-color);
}
ha-svg-icon.hassupdate,
ha-svg-icon.snapshot {
ha-svg-icon.backup {
color: var(--paper-item-icon-color);
}
ha-svg-icon.not_available {
@@ -122,7 +122,7 @@ class HassioCardContent extends LitElement {
}
.dot {
position: absolute;
background-color: var(--paper-orange-400);
background-color: var(--warning-color);
width: 12px;
height: 12px;
top: 8px;
@@ -1,6 +1,5 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiFolderUpload } from "@mdi/js";
import "@polymer/iron-input/iron-input";
import "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
@@ -9,23 +8,20 @@ import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload";
import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
HassioSnapshot,
uploadSnapshot,
} from "../../../src/data/hassio/snapshot";
import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types";
declare global {
interface HASSDomEvents {
"snapshot-uploaded": { snapshot: HassioSnapshot };
"backup-uploaded": { backup: HassioBackup };
}
}
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-snapshot")
export class HassioUploadSnapshot extends LitElement {
@customElement("hassio-upload-backup")
export class HassioUploadBackup extends LitElement {
public hass!: HomeAssistant;
@state() public value: string | null = null;
@@ -38,7 +34,7 @@ export class HassioUploadSnapshot extends LitElement {
.uploading=${this._uploading}
.icon=${mdiFolderUpload}
accept="application/x-tar"
label="Upload snapshot"
label="Upload backup"
@file-picked=${this._uploadFile}
auto-open-file-dialog
></ha-file-upload>
@@ -50,10 +46,10 @@ export class HassioUploadSnapshot extends LitElement {
if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, {
title: "Snapshot file is too big",
title: "Backup file is too big",
text: html`The maximum allowed filesize is 1GB.<br />
<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"
>Have a look here on how to restore it.</a
>`,
@@ -65,15 +61,15 @@ export class HassioUploadSnapshot extends LitElement {
if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, {
title: "Unsupported file format",
text: "Please choose a Home Assistant snapshot file (.tar)",
text: "Please choose a Home Assistant backup file (.tar)",
confirmText: "ok",
});
return;
}
this._uploading = true;
try {
const snapshot = await uploadSnapshot(this.hass, file);
fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data });
const backup = await uploadBackup(this.hass, file);
fireEvent(this, "backup-uploaded", { backup: backup.data });
} catch (err) {
showAlertDialog(this, {
title: "Upload failed",
@@ -88,6 +84,6 @@ export class HassioUploadSnapshot extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"hassio-upload-snapshot": HassioUploadSnapshot;
"hassio-upload-backup": HassioUploadBackup;
}
}
@@ -5,15 +5,16 @@ import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { formatDate } from "../../../src/common/datetime/format_date";
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
import { LocalizeFunc } from "../../../src/common/translations/localize";
import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio";
import {
HassioFullSnapshotCreateParams,
HassioPartialSnapshotCreateParams,
HassioSnapshotDetail,
} from "../../../src/data/hassio/snapshot";
HassioFullBackupCreateParams,
HassioPartialBackupCreateParams,
HassioBackupDetail,
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types";
@@ -44,6 +45,9 @@ const _computeFolders = (folders): CheckboxItem[] => {
if (folders.includes("share")) {
list.push({ slug: "share", name: "Share", checked: false });
}
if (folders.includes("media")) {
list.push({ slug: "media", name: "Media", checked: false });
}
if (folders.includes("addons/local")) {
list.push({ slug: "addons/local", name: "Local add-ons", checked: false });
}
@@ -60,15 +64,17 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
}))
.sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-snapshot-content")
export class SupervisorSnapshotContent extends LitElement {
@customElement("supervisor-backup-content")
export class SupervisorBackupContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc;
@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[];
@@ -76,96 +82,100 @@ export class SupervisorSnapshotContent extends LitElement {
@property({ type: Boolean }) public homeAssistant = false;
@property({ type: Boolean }) public snapshotHasPassword = false;
@property({ type: Boolean }) public backupHasPassword = false;
@property() public snapshotName = "";
@property({ type: Boolean }) public onboarding = false;
@property() public snapshotPassword = "";
@property() public backupName = "";
@property() public backupPassword = "";
@property() public confirmBackupPassword = "";
public willUpdate(changedProps) {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
this.folders = _computeFolders(
this.snapshot
? this.snapshot.folders
this.backup
? this.backup.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.snapshot
? this.snapshot.addons
: this.supervisor?.supervisor.addons
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
);
this.snapshotType = this.snapshot?.type || "full";
this.snapshotName = this.snapshot?.name || "";
this.snapshotHasPassword = this.snapshot?.protected || false;
this.backupType = this.backup?.type || "full";
this.backupName = this.backup?.name || "";
this.backupHasPassword = this.backup?.protected || false;
}
}
private _localize = (string: string) =>
this.supervisor?.localize(`backup.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult {
if (!this.supervisor) {
if (!this.onboarding && !this.supervisor) {
return html``;
}
const foldersSection =
this.snapshotType === "partial" ? this._getSection("folders") : undefined;
this.backupType === "partial" ? this._getSection("folders") : undefined;
const addonsSection =
this.snapshotType === "partial" ? this._getSection("addons") : undefined;
this.backupType === "partial" ? this._getSection("addons") : undefined;
return html`
${this.snapshot
${this.backup
? html`<div class="details">
${this.snapshot.type === "full"
? this.supervisor.localize("snapshot.full_snapshot")
: this.supervisor.localize("snapshot.partial_snapshot")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
${formatDateTime(new Date(this.snapshot.date), this.hass.locale)}
${this.backup.type === "full"
? this._localize("full_backup")
: this._localize("partial_backup")}
(${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br />
${this.hass
? formatDateTime(new Date(this.backup.date), this.hass.locale)
: this.backup.date}
</div>`
: html`<paper-input
name="snapshotName"
.label=${this.supervisor.localize("snapshot.name")}
.value=${this.snapshotName}
name="backupName"
.label=${this._localize("name")}
.value=${this.backupName}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`}
${!this.snapshot || this.snapshot.type === "full"
${!this.backup || this.backup.type === "full"
? html`<div class="sub-header">
${!this.snapshot
? this.supervisor.localize("snapshot.type")
: this.supervisor.localize("snapshot.select_type")}
${!this.backup
? this._localize("type")
: this._localize("select_type")}
</div>
<div class="snapshot-types">
<ha-formfield
.label=${this.supervisor.localize("snapshot.full_snapshot")}
>
<div class="backup-types">
<ha-formfield .label=${this._localize("full_backup")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="full"
name="snapshotType"
.checked=${this.snapshotType === "full"}
name="backupType"
.checked=${this.backupType === "full"}
>
</ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.supervisor!.localize("snapshot.partial_snapshot")}
>
<ha-formfield .label=${this._localize("partial_backup")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="partial"
name="snapshotType"
.checked=${this.snapshotType === "partial"}
name="backupType"
.checked=${this.backupType === "partial"}
>
</ha-radio>
</ha-formfield>
</div>`
: ""}
${this.snapshot && this.snapshotType === "partial"
? html`
${this.snapshot.homeassistant
${this.backupType === "partial"
? html`<div class="partial-picker">
${this.backup && this.backup.homeassistant
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.snapshot.homeassistant}
.version=${this.backup.homeassistant}
>
</supervisor-formfield-label>`}
>
@@ -179,15 +189,11 @@ export class SupervisorSnapshotContent extends LitElement {
</ha-formfield>
`
: ""}
`
: ""}
${this.snapshotType === "partial"
? html`
${foldersSection?.templates.length
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this.supervisor.localize("snapshot.folders")}
.label=${this._localize("folders")}
.iconPath=${mdiFolder}
>
</supervisor-formfield-label>`}
@@ -207,7 +213,7 @@ export class SupervisorSnapshotContent extends LitElement {
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this.supervisor.localize("snapshot.addons")}
.label=${this._localize("addons")}
.iconPath=${mdiPuzzle}
>
</supervisor-formfield-label>`}
@@ -223,29 +229,44 @@ export class SupervisorSnapshotContent extends LitElement {
<div class="section-content">${addonsSection.templates}</div>
`
: ""}
`
</div> `
: ""}
${!this.snapshot
${this.backupType === "partial" &&
(!this.backup || this.backupHasPassword)
? html`<hr />`
: ""}
${!this.backup
? html`<ha-formfield
.label=${this.supervisor.localize("snapshot.password_protection")}
class="password"
.label=${this._localize("password_protection")}
>
<ha-checkbox
.checked=${this.snapshotHasPassword}
.checked=${this.backupHasPassword}
@change=${this._toggleHasPassword}
>
</ha-checkbox
></ha-formfield>`
</ha-checkbox>
</ha-formfield>`
: ""}
${this.snapshotHasPassword
${this.backupHasPassword
? html`
<paper-input
.label=${this.supervisor.localize("snapshot.password")}
.label=${this._localize("password")}
type="password"
name="snapshotPassword"
.value=${this.snapshotPassword}
name="backupPassword"
.value=${this.backupPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>
${!this.backup
? html` <paper-input
.label=${this._localize("confirm_password")}
type="password"
name="confirmBackupPassword"
.value=${this.confirmBackupPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`
: ""}
`
: ""}
`;
@@ -253,21 +274,24 @@ export class SupervisorSnapshotContent extends LitElement {
static get styles(): CSSResultGroup {
return css`
ha-checkbox {
--mdc-checkbox-touch-target-size: 16px;
.partial-picker ha-formfield {
display: block;
margin: 4px 12px 8px 0;
}
ha-formfield {
display: contents;
.partial-picker ha-checkbox {
--mdc-checkbox-touch-target-size: 32px;
}
.partial-picker {
display: block;
margin: 0px -6px;
}
supervisor-formfield-label {
display: inline-flex;
align-items: center;
}
paper-input[type="password"] {
display: block;
margin: 4px 0 4px 16px;
hr {
border-color: var(--divider-color);
border-bottom: none;
margin: 16px 0;
}
.details {
color: var(--secondary-text-color);
@@ -275,13 +299,15 @@ export class SupervisorSnapshotContent extends LitElement {
.section-content {
display: flex;
flex-direction: column;
margin-left: 16px;
margin-left: 30px;
}
.security {
margin-top: 16px;
ha-formfield.password {
display: block;
margin: 0 -14px -16px;
}
.snapshot-types {
.backup-types {
display: flex;
margin-left: -13px;
}
.sub-header {
margin-top: 8px;
@@ -289,20 +315,23 @@ export class SupervisorSnapshotContent extends LitElement {
`;
}
public snapshotDetails():
| HassioPartialSnapshotCreateParams
| HassioFullSnapshotCreateParams {
public backupDetails():
| HassioPartialBackupCreateParams
| HassioFullBackupCreateParams {
const data: any = {};
if (!this.snapshot) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale);
if (!this.backup) {
data.name = this.backupName || formatDate(new Date(), this.hass.locale);
}
if (this.snapshotHasPassword) {
data.password = this.snapshotPassword;
if (this.backupHasPassword) {
data.password = this.backupPassword;
if (!this.backup) {
data.confirm_password = this.confirmBackupPassword;
}
}
if (this.snapshotType === "full") {
if (this.backupType === "full") {
return data;
}
@@ -331,7 +360,7 @@ export class SupervisorSnapshotContent extends LitElement {
const addons =
section === "addons"
? new Map(
this.supervisor!.addon.addons.map((item) => [item.slug, item])
this.supervisor?.addon.addons.map((item) => [item.slug, item])
)
: undefined;
let checkedItems = 0;
@@ -341,6 +370,7 @@ export class SupervisorSnapshotContent extends LitElement {
.label=${item.name}
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
.imageUrl=${section === "addons" &&
!this.onboarding &&
atLeastVersion(this.hass.config.version, 0, 105) &&
addons?.get(item.slug)?.icon
? `/api/hassio/addons/${item.slug}/icon`
@@ -383,7 +413,7 @@ export class SupervisorSnapshotContent extends LitElement {
}
private _toggleHasPassword(): void {
this.snapshotHasPassword = !this.snapshotHasPassword;
this.backupHasPassword = !this.backupHasPassword;
}
private _toggleSection(ev): void {
@@ -413,6 +443,6 @@ export class SupervisorSnapshotContent extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"supervisor-snapshot-content": SupervisorSnapshotContent;
"supervisor-backup-content": SupervisorBackupContent;
}
}
@@ -29,7 +29,6 @@ class SupervisorFormfieldLabel extends LitElement {
static get styles(): CSSResultGroup {
return css`
:host {
cursor: pointer;
display: flex;
align-items: center;
}
+2 -2
View File
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
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 { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles";
@@ -33,7 +33,7 @@ class HassioAddons extends LitElement {
</ha-card>
`
: this.supervisor.supervisor.addons
.sort((a, b) => compare(a.name, b.name))
.sort((a, b) => stringCompare(a.name, b.name))
.map(
(addon) => html`
<ha-card .addon=${addon} @click=${this._addonTapped}>
+2 -2
View File
@@ -86,7 +86,7 @@ export class HassioUpdate extends LitElement {
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
)}
${this.supervisor.host.features.includes("hassos")
${this.supervisor.host.features.includes("haos")
? this._renderUpdateCard(
"Operating System",
"os",
@@ -162,7 +162,7 @@ export class HassioUpdate extends LitElement {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
snapshotParams: {
backupParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
@@ -6,19 +6,20 @@ import "../../../../src/components/ha-header-bar";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-upload-snapshot";
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload";
import "../../components/hassio-upload-backup";
import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload";
@customElement("dialog-hassio-snapshot-upload")
export class DialogHassioSnapshotUpload
@customElement("dialog-hassio-backup-upload")
export class DialogHassioBackupUpload
extends LitElement
implements HassDialog<HassioSnapshotUploadDialogParams> {
implements HassDialog<HassioBackupUploadDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioSnapshotUploadDialogParams;
@state() private _params?: HassioBackupUploadDialogParams;
public async showDialog(
params: HassioSnapshotUploadDialogParams
params: HassioBackupUploadDialogParams
): Promise<void> {
this._params = params;
await this.updateComplete;
@@ -26,8 +27,8 @@ export class DialogHassioSnapshotUpload
public closeDialog(): void {
if (this._params && !this._params.onboarding) {
if (this._params.reloadSnapshot) {
this._params.reloadSnapshot();
if (this._params.reloadBackup) {
this._params.reloadBackup();
}
}
this._params = undefined;
@@ -50,23 +51,23 @@ export class DialogHassioSnapshotUpload
>
<div slot="heading">
<ha-header-bar>
<span slot="title"> Upload snapshot </span>
<span slot="title"> Upload backup </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
<hassio-upload-snapshot
@snapshot-uploaded=${this._snapshotUploaded}
<hassio-upload-backup
@backup-uploaded=${this._backupUploaded}
.hass=${this.hass}
></hassio-upload-snapshot>
></hassio-upload-backup>
</ha-dialog>
`;
}
private _snapshotUploaded(ev) {
const snapshot = ev.detail.snapshot;
this._params?.showSnapshot(snapshot.slug);
private _backupUploaded(ev) {
const backup = ev.detail.backup;
this._params?.showBackup(backup.slug);
this.closeDialog();
}
@@ -93,6 +94,6 @@ export class DialogHassioSnapshotUpload
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload;
"dialog-hassio-backup-upload": DialogHassioBackupUpload;
}
}
@@ -0,0 +1,351 @@
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiClose, mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
fetchHassioBackupInfo,
HassioBackupDetail,
} from "../../../../src/data/hassio/backup";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import { atLeastVersion } from "../../../../src/common/config/version";
@customElement("dialog-hassio-backup")
class HassioBackupDialog
extends LitElement
implements HassDialog<HassioBackupDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string;
@state() private _backup?: HassioBackupDetail;
@state() private _dialogParams?: HassioBackupDialogParams;
@state() private _restoringBackup = false;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
public async showDialog(params: HassioBackupDialogParams) {
this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
this._dialogParams = params;
this._restoringBackup = false;
}
public closeDialog() {
this._backup = undefined;
this._dialogParams = undefined;
this._restoringBackup = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams || !this._backup) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${true}
>
<div slot="heading">
<ha-header-bar>
<span slot="title">${this._backup.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
${this._restoringBackup
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
>
</supervisor-backup-content>`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<mwc-button
.disabled=${this._restoringBackup}
slot="secondaryAction"
@click=${this._restoreClicked}
>
Restore
</mwc-button>
${!this._dialogParams.onboarding
? html`<ha-button-menu
fixed
slot="primaryAction"
@action=${this._handleMenuAction}
@closed=${(ev: Event) => ev.stopPropagation()}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>Download Backup</mwc-list-item>
<mwc-list-item class="error">Delete Backup</mwc-list-item>
</ha-button-menu>`
: ""}
</ha-dialog>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-svg-icon {
color: var(--primary-text-color);
}
ha-circular-progress {
display: block;
text-align: center;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
`,
];
}
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._downloadClicked();
break;
case 1:
this._deleteClicked();
break;
}
}
private async _restoreClicked() {
const backupDetails = this._backupContent.backupDetails();
this._restoringBackup = true;
if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(backupDetails);
} else {
await this._partialRestoreClicked(backupDetails);
}
this._restoringBackup = false;
}
private async _partialRestoreClicked(backupDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore backup",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this backup?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
this.closeDialog();
}
}
private async _fullRestoreClicked(backupDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore backup",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title:
"Are you sure you want to wipe your system and restore this backup?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
this.closeDialog();
}
}
private async _deleteClicked() {
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this backup?",
confirmText: "delete",
dismissText: "cancel",
}))
) {
return;
}
this.hass
.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(
() => {
if (this._dialogParams!.onDelete) {
this._dialogParams!.onDelete();
}
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
}
private async _downloadClicked() {
let signedPath: { path: string };
try {
signedPath = await getSignedPath(
this.hass,
`/api/hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/download`
);
} catch (err) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
return;
}
if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, {
title: "Potential slow download",
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",
dismissText: "cancel",
});
if (!confirm) {
return;
}
}
fileDownload(
this,
signedPath.path,
`home_assistant_backup_${slugify(this._computeName)}.tar`
);
}
private get _computeName() {
return this._backup
? this._backup.name || this._backup.slug
: "Unnamed backup";
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-backup": HassioBackupDialog;
}
}
@@ -2,41 +2,42 @@ import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
} from "../../../../src/data/hassio/snapshot";
createHassioFullBackup,
createHassioPartialBackup,
} from "../../../../src/data/hassio/backup";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot";
import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
@customElement("dialog-hassio-create-snapshot")
class HassioCreateSnapshotDialog extends LitElement {
@customElement("dialog-hassio-create-backup")
class HassioCreateBackupDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams;
@state() private _dialogParams?: HassioCreateBackupDialogParams;
@state() private _error?: string;
@state() private _creatingSnapshot = false;
@state() private _creatingBackup = false;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
public showDialog(params: HassioCreateSnapshotDialogParams) {
public showDialog(params: HassioCreateBackupDialogParams) {
this._dialogParams = params;
this._creatingSnapshot = false;
this._creatingBackup = false;
}
public closeDialog() {
this._dialogParams = undefined;
this._creatingSnapshot = false;
this._creatingBackup = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -52,65 +53,76 @@ class HassioCreateSnapshotDialog extends LitElement {
@closed=${this.closeDialog}
.heading=${createCloseHeading(
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`<supervisor-snapshot-content
: html`<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
>
</supervisor-snapshot-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
</supervisor-backup-content>`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")}
</mwc-button>
<mwc-button
.disabled=${this._creatingSnapshot}
.disabled=${this._creatingBackup}
slot="primaryAction"
@click=${this._createSnapshot}
@click=${this._createBackup}
>
${this._dialogParams.supervisor.localize("snapshot.create")}
${this._dialogParams.supervisor.localize("backup.create")}
</mwc-button>
</ha-dialog>
`;
}
private async _createSnapshot(): Promise<void> {
private async _createBackup(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize(
"snapshot.could_not_create"
"backup.could_not_create"
),
text: this._dialogParams!.supervisor.localize(
"snapshot.create_blocked_not_running",
"backup.create_blocked_not_running",
"state",
this._dialogParams!.supervisor.info.state
),
});
return;
}
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._creatingSnapshot = true;
const backupDetails = this._backupContent.backupDetails();
this._creatingBackup = true;
this._error = "";
if (backupDetails.password && !backupDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize(
"backup.enter_password"
);
this._creatingBackup = false;
return;
}
if (
this._snapshotContent.snapshotHasPassword &&
!this._snapshotContent.snapshotPassword.length
backupDetails.password &&
backupDetails.password !== backupDetails.confirm_password
) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password"
"backup.passwords_not_matching"
);
this._creatingSnapshot = false;
this._creatingBackup = false;
return;
}
delete backupDetails.confirm_password;
try {
if (this._snapshotContent.snapshotType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails);
if (this._backupContent.backupType === "full") {
await createHassioFullBackup(this.hass, backupDetails);
} else {
await createHassioPartialSnapshot(this.hass, snapshotDetails);
await createHassioPartialBackup(this.hass, backupDetails);
}
this._dialogParams!.onCreate();
@@ -118,7 +130,7 @@ class HassioCreateSnapshotDialog extends LitElement {
} catch (err) {
this._error = extractApiErrorMessage(err);
}
this._creatingSnapshot = false;
this._creatingBackup = false;
}
static get styles(): CSSResultGroup {
@@ -137,6 +149,6 @@ class HassioCreateSnapshotDialog extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog;
"dialog-hassio-create-backup": HassioCreateBackupDialog;
}
}
@@ -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,
});
};
@@ -1,20 +1,22 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioSnapshotDialogParams {
export interface HassioBackupDialogParams {
slug: string;
onDelete?: () => void;
onboarding?: boolean;
supervisor?: Supervisor;
localize?: LocalizeFunc;
}
export const showHassioSnapshotDialog = (
export const showHassioBackupDialog = (
element: HTMLElement,
dialogParams: HassioSnapshotDialogParams
dialogParams: HassioBackupDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot",
dialogImport: () => import("./dialog-hassio-snapshot"),
dialogTag: "dialog-hassio-backup",
dialogImport: () => import("./dialog-hassio-backup"),
dialogParams,
});
};
@@ -1,18 +1,18 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioCreateSnapshotDialogParams {
export interface HassioCreateBackupDialogParams {
supervisor: Supervisor;
onCreate: () => void;
}
export const showHassioCreateSnapshotDialog = (
export const showHassioCreateBackupDialog = (
element: HTMLElement,
dialogParams: HassioCreateSnapshotDialogParams
dialogParams: HassioCreateBackupDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-create-snapshot",
dialogImport: () => import("./dialog-hassio-create-snapshot"),
dialogTag: "dialog-hassio-create-backup",
dialogImport: () => import("./dialog-hassio-create-backup"),
dialogParams,
});
};
+194
View File
@@ -0,0 +1,194 @@
import { mdiClose } from "@mdi/js";
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/common/search/search-input";
import { stringCompare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { dump } from "../../../../src/resources/js-yaml-dump";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
const _filterDevices = memoizeOne(
(showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) =>
hardware.devices
.filter(
(device) =>
(showAdvanced ||
["tty", "gpio", "input"].includes(device.subsystem)) &&
(device.by_id?.toLowerCase().includes(filter) ||
device.name.toLowerCase().includes(filter) ||
device.dev_path.toLocaleLowerCase().includes(filter) ||
JSON.stringify(device.attributes)
.toLocaleLowerCase()
.includes(filter))
)
.sort((a, b) => stringCompare(a.name, b.name))
);
@customElement("dialog-hassio-hardware")
class HassioHardwareDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioHardwareDialogParams;
@state() private _filter?: string;
public showDialog(params: HassioHardwareDialogParams) {
this._dialogParams = params;
}
public closeDialog() {
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
const devices = _filterDevices(
this.hass.userData?.showAdvanced || false,
this._dialogParams.hardware,
(this._filter || "").toLowerCase()
);
return html`
<ha-dialog
open
scrimClickAction
hideActions
@closed=${this.closeDialog}
.heading=${true}
>
<div class="header" slot="heading">
<h2>
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
</h2>
<mwc-icon-button dialogAction="close">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<search-input
autofocus
no-label-float
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
.label=${this._dialogParams.supervisor.localize(
"dialog.hardware.search"
)}
>
</search-input>
</div>
${devices.map(
(device) =>
html`<ha-expansion-panel
.header=${device.name}
.secondary=${device.by_id || undefined}
outlined
>
<div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.subsystem"
)}:
</span>
<span>${device.subsystem}</span>
</div>
<div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.device_path"
)}:
</span>
<code>${device.dev_path}</code>
</div>
${device.by_id
? html` <div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.id"
)}:
</span>
<code>${device.by_id}</code>
</div>`
: ""}
<div class="attributes">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.attributes"
)}:
</span>
<pre>${dump(device.attributes, { indent: 2 })}</pre>
</div>
</ha-expansion-panel>`
)}
</ha-dialog>
`;
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
mwc-icon-button {
position: absolute;
right: 16px;
top: 10px;
text-decoration: none;
color: var(--primary-text-color);
}
h2 {
margin: 18px 42px 0 18px;
color: var(--primary-text-color);
}
ha-expansion-panel {
margin: 4px 0;
}
pre,
code {
background-color: var(--markdown-code-background-color, none);
border-radius: 3px;
}
pre {
padding: 16px;
overflow: auto;
line-height: 1.45;
font-family: var(--code-font-family, monospace);
}
code {
font-size: 85%;
padding: 0.2em 0.4em;
}
search-input {
margin: 0 16px;
display: block;
}
.device-property {
display: flex;
justify-content: space-between;
}
.attributes {
margin-top: 12px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-hardware": HassioHardwareDialog;
}
}
@@ -0,0 +1,19 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioHardwareDialogParams {
supervisor: Supervisor;
hardware: HassioHardwareInfo;
}
export const showHassioHardwareDialog = (
element: HTMLElement,
dialogParams: HassioHardwareDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-hardware",
dialogImport: () => import("./dialog-hassio-hardware"),
dialogParams,
});
};
@@ -61,10 +61,6 @@ class HassioMarkdownDialog extends LitElement {
app-toolbar [main-title] {
margin-left: 16px;
}
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-paper-dialog {
max-height: 100%;
@@ -10,6 +10,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
@@ -41,7 +42,8 @@ const IP_VERSIONS = ["ipv4", "ipv6"];
@customElement("dialog-hassio-network")
export class DialogHassioNetwork
extends LitElement
implements HassDialog<HassioNetworkDialogParams> {
implements HassDialog<HassioNetworkDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -107,7 +109,7 @@ export class DialogHassioNetwork
</mwc-icon-button>
</ha-header-bar>
${this._interfaces.length > 1
? html` <mwc-tab-bar
? html`<mwc-tab-bar
.activeIndex=${this._curTabIndex}
@MDCTabBar:activated=${this._handleTabActivated}
>${this._interfaces.map(
@@ -250,9 +252,9 @@ export class DialogHassioNetwork
`
: ""}
${this._dirty
? html`<div class="warning">
? html`<ha-alert alert-type="warning">
${this.supervisor.localize("dialog.network.warning")}
</div>`
</ha-alert>`
: ""}
</div>
<div class="buttons">
@@ -492,7 +494,7 @@ export class DialogHassioNetwork
}
private _handleRadioValueChangedAp(ev: CustomEvent): void {
const value = ((ev.target as any).value as string) as
const value = (ev.target as any).value as string as
| "open"
| "wep"
| "wpa-psk";
@@ -161,9 +161,9 @@ class HassioRegistriesDialog extends LitElement {
public focus(): void {
this.updateComplete.then(() =>
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
);
}
@@ -9,6 +9,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
@@ -75,7 +76,9 @@ class HassioRepositoriesDialog extends LitElement {
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">
${repositories.length
? repositories.map(
@@ -161,9 +164,9 @@ class HassioRepositoriesDialog extends LitElement {
public focus() {
this.updateComplete.then(() =>
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
);
}
@@ -1,310 +0,0 @@
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
fetchHassioSnapshotInfo,
HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog
extends LitElement
implements HassDialog<HassioSnapshotDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string;
@state() private _snapshot?: HassioSnapshotDetail;
@state() private _dialogParams?: HassioSnapshotDialogParams;
@state() private _restoringSnapshot = false;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._dialogParams = params;
this._restoringSnapshot = false;
}
public closeDialog() {
this._snapshot = undefined;
this._dialogParams = undefined;
this._restoringSnapshot = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams || !this._snapshot) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${createCloseHeading(this.hass, this._computeName)}
>
${this._restoringSnapshot
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.snapshot=${this._snapshot}
>
</supervisor-snapshot-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button
.disabled=${this._restoringSnapshot}
slot="secondaryAction"
@click=${this._restoreClicked}
>
Restore
</mwc-button>
<ha-button-menu
fixed
slot="primaryAction"
@action=${this._handleMenuAction}
@closed=${(ev: Event) => ev.stopPropagation()}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>Download Snapshot</mwc-list-item>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
</ha-button-menu>
</ha-dialog>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-svg-icon {
color: var(--primary-text-color);
}
ha-circular-progress {
display: block;
text-align: center;
}
`,
];
}
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._downloadClicked();
break;
case 1:
this._deleteClicked();
break;
}
}
private async _restoreClicked() {
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._restoringSnapshot = true;
if (this._snapshotContent.snapshotType === "full") {
await this._fullRestoreClicked(snapshotDetails);
} else {
await this._partialRestoreClicked(snapshotDetails);
}
this._restoringSnapshot = false;
}
private async _partialRestoreClicked(snapshotDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
snapshotDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(snapshotDetails),
});
this.closeDialog();
}
}
private async _fullRestoreClicked(snapshotDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title:
"Are you sure you want to wipe your system and restore this snapshot?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`,
snapshotDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
method: "POST",
body: JSON.stringify(snapshotDetails),
});
this.closeDialog();
}
}
private async _deleteClicked() {
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this snapshot?",
confirmText: "delete",
dismissText: "cancel",
}))
) {
return;
}
this.hass
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
.then(
() => {
if (this._dialogParams!.onDelete) {
this._dialogParams!.onDelete();
}
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
}
private async _downloadClicked() {
let signedPath: { path: string };
try {
signedPath = await getSignedPath(
this.hass,
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
);
} catch (err) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
return;
}
if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, {
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?",
confirmText: "continue",
dismissText: "cancel",
});
if (!confirm) {
return;
}
}
const a = document.createElement("a");
a.href = signedPath.path;
a.download = `home_assistant_snapshot_${slugify(this._computeName)}.tar`;
this.shadowRoot!.appendChild(a);
a.click();
this.shadowRoot!.removeChild(a);
}
private get _computeName() {
return this._snapshot
? this._snapshot.name || this._snapshot.slug
: "Unnamed snapshot";
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-snapshot": HassioSnapshotDialog;
}
}
@@ -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,
});
};
@@ -2,6 +2,7 @@ import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row";
@@ -11,7 +12,7 @@ import {
extractApiErrorMessage,
ignoreSupervisorError,
} 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 type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
@@ -22,9 +23,9 @@ class DialogSupervisorUpdate extends LitElement {
@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;
@@ -41,7 +42,7 @@ class DialogSupervisorUpdate extends LitElement {
public closeDialog(): void {
this._action = null;
this._createSnapshot = true;
this._createBackup = true;
this._error = undefined;
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
@@ -49,9 +50,9 @@ class DialogSupervisorUpdate extends LitElement {
public focus(): void {
this.updateComplete.then(() =>
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
);
}
@@ -84,20 +85,20 @@ class DialogSupervisorUpdate extends LitElement {
<ha-settings-row>
<span slot="heading">
${this._dialogParams.supervisor.localize(
"dialog.update.snapshot"
"dialog.update.backup"
)}
</span>
<span slot="description">
${this._dialogParams.supervisor.localize(
"dialog.update.create_snapshot",
"dialog.update.create_backup",
"name",
this._dialogParams.name
)}
</span>
<ha-switch
.checked=${this._createSnapshot}
.checked=${this._createBackup}
haptic
@click=${this._toggleSnapshot}
@click=${this._toggleBackup}
>
</ha-switch>
</ha-settings-row>
@@ -123,27 +124,29 @@ class DialogSupervisorUpdate extends LitElement {
this._dialogParams.version
)
: this._dialogParams.supervisor.localize(
"dialog.update.snapshotting",
"dialog.update.creating_backup",
"name",
this._dialogParams.name
)}
</p>`}
${this._error ? html`<p class="error">${this._error}</p>` : ""}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
</ha-dialog>
`;
}
private _toggleSnapshot() {
this._createSnapshot = !this._createSnapshot;
private _toggleBackup() {
this._createBackup = !this._createBackup;
}
private async _update() {
if (this._createSnapshot) {
this._action = "snapshot";
if (this._createBackup) {
this._action = "backup";
try {
await createHassioPartialSnapshot(
await createHassioPartialBackup(
this.hass,
this._dialogParams!.snapshotParams
this._dialogParams!.backupParams
);
} catch (err) {
this._error = extractApiErrorMessage(err);
@@ -158,8 +161,8 @@ class DialogSupervisorUpdate extends LitElement {
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
this._action = null;
}
this._action = null;
return;
}
@@ -5,7 +5,7 @@ export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor;
name: string;
version: string;
snapshotParams: any;
backupParams: any;
updateHandler: () => Promise<void>;
}
+1 -1
View File
@@ -121,7 +121,7 @@ export class HassioMain extends SupervisorBaseElement {
}
} else {
themeName =
((this.hass.selectedTheme as unknown) as string) ||
(this.hass.selectedTheme as unknown as string) ||
this.hass.themes.default_theme;
}
+8 -5
View File
@@ -1,19 +1,19 @@
import { html, LitElement, TemplateResult } from "lit";
import { sanitizeUrl } from "@braintree/sanitize-url";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../src/common/navigate";
import {
createSearchParam,
extractSearchParamsObject,
} from "../../src/common/url/search-params";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import "../../src/layouts/hass-error-screen";
import {
ParamType,
Redirect,
Redirects,
} from "../../src/panels/my/ha-panel-my";
import { navigate } from "../../src/common/navigate";
import { HomeAssistant, Route } from "../../src/types";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
const REDIRECTS: Redirects = {
supervisor: {
@@ -26,7 +26,10 @@ const REDIRECTS: Redirects = {
redirect: "/hassio/system",
},
supervisor_snapshots: {
redirect: "/hassio/snapshots",
redirect: "/hassio/backups",
},
supervisor_backups: {
redirect: "/hassio/backups",
},
supervisor_store: {
redirect: "/hassio/store",
+5 -3
View File
@@ -9,7 +9,7 @@ import "./addon-store/hassio-addon-store";
// Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard";
// 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";
@customElement("hassio-panel-router")
@@ -23,6 +23,8 @@ class HassioPanelRouter extends HassRouterPage {
@property({ type: Boolean }) public narrow!: boolean;
protected routerOptions: RouterOptions = {
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
routes: {
dashboard: {
tag: "hassio-dashboard",
@@ -30,8 +32,8 @@ class HassioPanelRouter extends HassRouterPage {
store: {
tag: "hassio-addon-store",
},
snapshots: {
tag: "hassio-snapshots",
backups: {
tag: "hassio-backups",
},
system: {
tag: "hassio-system",
+3 -1
View File
@@ -23,6 +23,8 @@ class HassioRouter extends HassRouterPage {
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
initialLoad: () => this._redirectIngress(),
showLoading: true,
routes: {
@@ -30,7 +32,7 @@ class HassioRouter extends HassRouterPage {
tag: "hassio-panel",
cache: true,
},
snapshots: "dashboard",
backups: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {
+2 -2
View File
@@ -13,8 +13,8 @@ export const supervisorTabs: PageNavigation[] = [
iconPath: mdiStore,
},
{
translationKey: "panel.snapshots",
path: `/hassio/snapshots`,
translationKey: "panel.backups",
path: `/hassio/backups`,
iconPath: mdiBackupRestore,
},
{
+10 -3
View File
@@ -97,16 +97,23 @@ class HassioIngressView extends LitElement {
title: requestedAddon,
});
await nextRender();
history.back();
navigate("/hassio/store", { replace: true });
return;
}
if (!addonInfo.ingress) {
if (!addonInfo.version) {
await showAlertDialog(this, {
text: this.supervisor.localize("my.error_addon_not_installed"),
title: addonInfo.name,
});
await nextRender();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
} else if (!addonInfo.ingress) {
await showAlertDialog(this, {
text: this.supervisor.localize("my.error_addon_no_ingress"),
title: addonInfo.name,
});
await nextRender();
history.back();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
} else {
navigate(`/hassio/ingress/${addonInfo.slug}`, { replace: true });
}
+2 -4
View File
@@ -86,10 +86,8 @@ export class SupervisorBaseElement extends urlSyncMixin(
const unsubs = Object.keys(this._unsubs);
for (const collection of Object.keys(this._collections)) {
if (!unsubs.includes(collection)) {
this._unsubs[collection] = this._collections[
collection
].subscribe((data) =>
this._updateSupervisor({ [collection]: data })
this._unsubs[collection] = this._collections[collection].subscribe(
(data) => this._updateSupervisor({ [collection]: data })
);
}
}
+1 -1
View File
@@ -165,7 +165,7 @@ class HassioCoreInfo extends LitElement {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
snapshotParams: {
backupParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
+56 -21
View File
@@ -1,8 +1,6 @@
import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { dump } from "js-yaml";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -20,6 +18,7 @@ import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
changeHostOptions,
configSyncOS,
dataDiskMove,
rebootHost,
shutdownHost,
updateOS,
@@ -41,8 +40,8 @@ import {
roundWithOneDecimal,
} from "../../../src/util/calculate";
import "../components/supervisor-metric";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-host-info")
@@ -114,7 +113,7 @@ class HassioHostInfo extends LitElement {
`
: ""}
</ha-settings-row>
${!this.supervisor.host.features.includes("hassos")
${!this.supervisor.host.features.includes("haos")
? html`<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.host.docker_version")}
@@ -181,21 +180,27 @@ class HassioHostInfo extends LitElement {
`
: ""}
<ha-button-menu
corner="BOTTOM_START"
@action=${this._handleMenuAction}
>
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>
<mwc-list-item @click=${() => this._handleMenuAction("hardware")}>
${this.supervisor.localize("system.host.hardware")}
</mwc-list-item>
${this.supervisor.host.features.includes("hassos")
? html`<mwc-list-item>
${this.supervisor.host.features.includes("haos")
? html`<mwc-list-item
@click=${() => this._handleMenuAction("import_from_usb")}
>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>`
: ""}
${this.supervisor.host.features.includes("agent")
? html`<mwc-list-item
@click=${() => this._handleMenuAction("data_disk_move")}
>
${this.supervisor.localize("system.host.data_disk_move")}
</mwc-list-item>`
: ""}
</ha-button-menu>
</div>
</ha-card>
@@ -217,32 +222,34 @@ class HassioHostInfo extends LitElement {
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
});
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
private async _handleMenuAction(action: string) {
switch (action) {
case "hardware":
await this._showHardware();
break;
case 1:
case "import_from_usb":
await this._importFromUSB();
break;
case "data_disk_move":
await this._dataDiskMove();
break;
}
}
private async _showHardware(): Promise<void> {
let hardware;
try {
const content = await fetchHassioHardwareInfo(this.hass);
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("system.host.hardware"),
content: `<pre>${dump(content, { indent: 2 })}</pre>`,
});
hardware = await fetchHassioHardwareInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
await showAlertDialog(this, {
title: this.supervisor.localize(
"system.host.failed_to_get_hardware_list"
),
text: extractApiErrorMessage(err),
});
return;
}
showHassioHardwareDialog(this, { supervisor: this.supervisor, hardware });
}
private async _hostReboot(ev: CustomEvent): Promise<void> {
@@ -397,6 +404,34 @@ class HassioHostInfo extends LitElement {
}
}
private async _dataDiskMove(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.supervisor.localize("system.host.data_disk_move"),
text: html`${this.supervisor.localize(
"dialog.data_disk_move.description",
{ current_path: this.supervisor.os.data_disk }
)} <br /><br />${this.supervisor.localize(
"dialog.data_disk_move.confirm_text"
)}`,
confirmText: this.supervisor.localize("dialog.data_disk_move.move"),
dismissText: this.supervisor.localize("dialog.data_disk_move.cancel"),
});
if (!confirmed) {
return;
}
try {
await dataDiskMove(this.hass);
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_move"),
text: extractApiErrorMessage(err),
});
}
}
}
private async _loadData(): Promise<void> {
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
fireEvent(this, "supervisor-collection-refresh", {
+13 -18
View File
@@ -3,6 +3,7 @@ import { customElement, property, state } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-switch";
@@ -170,31 +171,25 @@ class HassioSupervisorInfo extends LitElement {
></ha-switch>
</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(
"system.supervisor.unsupported_title"
)}
<button
class="link"
.title=${this.supervisor.localize("common.learn_more")}
@click=${this._unsupportedDialog}
>
Learn more
</button>
</div>`}
</ha-alert>`}
${!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(
"system.supervisor.unhealthy_title"
)}
<button
class="link"
.title=${this.supervisor.localize("common.learn_more")}
@click=${this._unhealthyDialog}
>
Learn more
</button>
</div>`
</ha-alert>`
: ""}
</div>
<div class="metrics-block">
+4 -5
View File
@@ -5,6 +5,7 @@ import "@polymer/paper-listbox/paper-listbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
@@ -67,7 +68,9 @@ class HassioSupervisorLog extends LitElement {
protected render(): TemplateResult | void {
return html`
<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
? html`
<paper-dropdown-menu
@@ -154,10 +157,6 @@ class HassioSupervisorLog extends LitElement {
padding: 0 2%;
width: 96%;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
`,
];
}
+1 -2
View File
@@ -1,5 +1,4 @@
module.exports = {
"*.ts": () => "tsc -p tsconfig.json",
"*.{js,ts}": "eslint --fix",
"*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
};
+3
View File
@@ -0,0 +1,3 @@
[build.environment]
YARN_VERSION = "1.22.11"
NODE_OPTIONS = "--max_old_space_size=4096"
+104 -107
View File
@@ -17,120 +17,117 @@
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
"format": "yarn run format:eslint && yarn run format:prettier",
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
"test": "yarn run lint && yarn run mocha"
"test": "yarn run mocha"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@braintree/sanitize-url": "^5.0.1",
"@codemirror/commands": "^0.18.0",
"@codemirror/gutter": "^0.18.0",
"@codemirror/highlight": "^0.18.0",
"@codemirror/history": "^0.18.0",
"@codemirror/legacy-modes": "^0.18.0",
"@codemirror/rectangular-selection": "^0.18.0",
"@codemirror/search": "^0.18.0",
"@codemirror/state": "^0.18.0",
"@codemirror/stream-parser": "^0.18.0",
"@codemirror/text": "^0.18.0",
"@codemirror/view": "^0.18.0",
"@formatjs/intl-getcanonicallocales": "^1.5.10",
"@formatjs/intl-locale": "^2.4.28",
"@formatjs/intl-pluralrules": "^4.0.22",
"@fullcalendar/common": "5.1.0",
"@fullcalendar/core": "5.1.0",
"@fullcalendar/daygrid": "5.1.0",
"@fullcalendar/interaction": "5.1.0",
"@fullcalendar/list": "5.1.0",
"@lit-labs/virtualizer": "^0.6.0",
"@material/chips": "=12.0.0-canary.1a8d06483.0",
"@material/mwc-button": "canary",
"@material/mwc-checkbox": "canary",
"@material/mwc-circular-progress": "canary",
"@material/mwc-dialog": "canary",
"@material/mwc-fab": "canary",
"@material/mwc-formfield": "canary",
"@material/mwc-icon-button": "canary",
"@material/mwc-list": "canary",
"@material/mwc-menu": "canary",
"@material/mwc-radio": "canary",
"@material/mwc-ripple": "canary",
"@material/mwc-switch": "canary",
"@material/mwc-tab": "canary",
"@material/mwc-tab-bar": "canary",
"@material/top-app-bar": "=12.0.0-canary.1a8d06483.0",
"@mdi/js": "5.9.55",
"@mdi/svg": "5.9.55",
"@polymer/app-layout": "^3.0.2",
"@polymer/app-storage": "^3.0.2",
"@polymer/iron-autogrow-textarea": "^3.0.1",
"@braintree/sanitize-url": "^5.0.2",
"@codemirror/commands": "^0.19.2",
"@codemirror/gutter": "^0.19.1",
"@codemirror/highlight": "^0.19.2",
"@codemirror/history": "^0.19.0",
"@codemirror/legacy-modes": "^0.19.0",
"@codemirror/rectangular-selection": "^0.19.0",
"@codemirror/search": "^0.19.0",
"@codemirror/state": "^0.19.1",
"@codemirror/stream-parser": "^0.19.1",
"@codemirror/text": "^0.19.2",
"@codemirror/view": "^0.19.4",
"@formatjs/intl-getcanonicallocales": "^1.7.3",
"@formatjs/intl-locale": "^2.4.37",
"@formatjs/intl-pluralrules": "^4.1.3",
"@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",
"@material/chips": "12.0.0-canary.22d29cbb4.0",
"@material/data-table": "12.0.0-canary.22d29cbb4.0",
"@material/mwc-button": "0.22.1",
"@material/mwc-checkbox": "0.22.1",
"@material/mwc-circular-progress": "0.22.1",
"@material/mwc-dialog": "0.22.1",
"@material/mwc-fab": "0.22.1",
"@material/mwc-formfield": "0.22.1",
"@material/mwc-icon-button": "0.22.1",
"@material/mwc-linear-progress": "0.22.1",
"@material/mwc-list": "0.22.1",
"@material/mwc-menu": "0.22.1",
"@material/mwc-radio": "0.22.1",
"@material/mwc-ripple": "0.22.1",
"@material/mwc-switch": "0.22.1",
"@material/mwc-tab": "0.22.1",
"@material/mwc-tab-bar": "0.22.1",
"@material/top-app-bar": "12.0.0-canary.22d29cbb4.0",
"@mdi/js": "6.1.95",
"@mdi/svg": "6.1.95",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
"@polymer/iron-image": "^3.0.1",
"@polymer/iron-input": "^3.0.1",
"@polymer/iron-label": "^3.0.1",
"@polymer/iron-overlay-behavior": "^3.0.2",
"@polymer/iron-overlay-behavior": "^3.0.3",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/paper-checkbox": "^3.1.0",
"@polymer/paper-dialog": "^3.0.1",
"@polymer/paper-dialog-behavior": "^3.0.1",
"@polymer/paper-dialog-scrollable": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.0.1",
"@polymer/paper-input": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.2.0",
"@polymer/paper-input": "^3.2.1",
"@polymer/paper-item": "^3.0.1",
"@polymer/paper-listbox": "^3.0.1",
"@polymer/paper-menu-button": "^3.0.1",
"@polymer/paper-menu-button": "^3.1.0",
"@polymer/paper-progress": "^3.0.1",
"@polymer/paper-radio-button": "^3.0.1",
"@polymer/paper-radio-group": "^3.0.1",
"@polymer/paper-ripple": "^3.0.1",
"@polymer/paper-ripple": "^3.0.2",
"@polymer/paper-slider": "^3.0.1",
"@polymer/paper-styles": "^3.0.1",
"@polymer/paper-tabs": "^3.0.1",
"@polymer/paper-tabs": "^3.1.0",
"@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.5.2",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.4",
"@vaadin/vaadin-combo-box": "^20.0.4",
"@vaadin/vaadin-date-picker": "^20.0.4",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
"@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "^2.9.4",
"chartjs-chart-timeline": "^0.4.0",
"@webcomponents/webcomponentsjs": "^2.2.10",
"chart.js": "^3.3.2",
"comlink": "^4.3.1",
"core-js": "^3.6.5",
"cropperjs": "^1.5.11",
"core-js": "^3.15.2",
"cropperjs": "^1.5.12",
"date-fns": "^2.23.0",
"deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1",
"fecha": "^4.2.0",
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.0.4",
"home-assistant-js-websocket": "^5.10.0",
"idb-keyval": "^5.0.5",
"intl-messageformat": "^9.6.16",
"hls.js": "^1.0.10",
"home-assistant-js-websocket": "^5.11.1",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",
"leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4",
"lit": "^2.0.0-rc.2",
"lit": "^2.0.0-rc.3",
"lit-vaadin-helpers": "^0.1.3",
"marked": "^2.0.5",
"mdn-polyfills": "^5.16.0",
"marked": "^3.0.2",
"memoize-one": "^5.2.1",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "^0.3.1",
"proxy-polyfill": "^0.3.2",
"punycode": "^2.1.1",
"qrcode": "^1.4.4",
"regenerator-runtime": "^0.13.2",
"regenerator-runtime": "^0.13.8",
"resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0",
"sortablejs": "^1.10.2",
"sortablejs": "^1.14.0",
"superstruct": "^0.15.2",
"tinykeys": "^1.1.3",
"tsparticles": "^1.19.2",
"tsparticles": "^1.34.0",
"unfetch": "^4.1.0",
"vis-data": "^7.1.2",
"vis-network": "^8.5.4",
@@ -146,17 +143,17 @@
"xss": "^1.0.9"
},
"devDependencies": {
"@babel/core": "^7.14.3",
"@babel/plugin-external-helpers": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-decorators": "^7.13.15",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
"@babel/plugin-proposal-object-rest-spread": "^7.13.8",
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@babel/core": "^7.14.6",
"@babel/plugin-external-helpers": "^7.14.5",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-decorators": "^7.14.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.14.7",
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/preset-env": "^7.14.2",
"@babel/preset-typescript": "^7.13.0",
"@babel/preset-env": "^7.14.7",
"@babel/preset-typescript": "^7.14.5",
"@koa/cors": "^3.1.0",
"@open-wc/dev-server-hmr": "^0.0.2",
"@rollup/plugin-babel": "^5.2.1",
@@ -166,29 +163,29 @@
"@rollup/plugin-replace": "^2.3.2",
"@types/chromecast-caf-receiver": "5.0.12",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/js-yaml": "^4.0.1",
"@types/leaflet": "^1.7.0",
"@types/leaflet-draw": "^1.0.3",
"@types/marked": "^2.0.3",
"@types/mocha": "^8.2.2",
"@types/sortablejs": "^1.10.6",
"@types/js-yaml": "^4",
"@types/leaflet": "^1",
"@types/leaflet-draw": "^1",
"@types/marked": "^2",
"@types/mocha": "^8",
"@types/sortablejs": "^1",
"@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.28.3",
"@web/dev-server": "^0.0.24",
"@web/dev-server-rollup": "^0.2.11",
"babel-loader": "^8.1.0",
"babel-loader": "^8.2.2",
"chai": "^4.3.4",
"cpx": "^1.5.0",
"del": "^4.0.0",
"eslint": "^7.25.0",
"eslint": "^7.30.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.0",
"eslint-import-resolver-webpack": "^0.13.1",
"eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-lit": "^1.3.0",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-lit": "^1.5.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-unused-imports": "^1.1.2",
"eslint-plugin-wc": "^1.3.0",
"fancy-log": "^1.3.3",
"fs-extra": "^7.0.1",
@@ -200,7 +197,7 @@
"gulp-zopfli-green": "^3.0.1",
"html-minifier": "^4.0.0",
"husky": "^1.3.1",
"lint-staged": "^10.5.4",
"lint-staged": "^11.0.1",
"lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0",
"magic-string": "^0.25.7",
@@ -209,8 +206,7 @@
"mocha": "^8.4.0",
"object-hash": "^2.0.3",
"open": "^7.0.4",
"prettier": "^2.0.4",
"raw-loader": "^2.0.0",
"prettier": "^2.3.2",
"require-dir": "^1.2.0",
"rollup": "^2.8.2",
"rollup-plugin-string": "^3.0.0",
@@ -220,25 +216,26 @@
"sinon": "^11.0.0",
"source-map-url": "^0.4.0",
"systemjs": "^6.3.2",
"terser-webpack-plugin": "^5.1.2",
"terser-webpack-plugin": "^5.1.4",
"ts-lit-plugin": "^1.2.1",
"ts-mocha": "^8.0.0",
"typescript": "^4.2.4",
"typescript": "^4.3.5",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"webpack": "^5.24.1",
"webpack-cli": "^4.5.0",
"webpack": "^5.43.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"webpack-manifest-plugin": "^3.0.0",
"webpack-manifest-plugin": "^3.1.1",
"workbox-build": "^6.1.5"
},
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10",
"@polymer/polymer": "3.1.0",
"lit-html": "2.0.0-rc.3",
"lit-element": "3.0.0-rc.2"
"lit": "^2.0.0-rc.3",
"lit-html": "2.0.0-rc.4",
"lit-element": "3.0.0-rc.3",
"@lit/reactive-element": "1.0.0-rc.3"
},
"main": "src/home-assistant.js",
"husky": {
+8 -14
View File
@@ -9,12 +9,6 @@ if [ -z "${DEVCONTAINER}" ]; then
exit 1
fi
if [ ! -z "${CODESPACES}" ]; then
WORKSPACE="/root/workspace/frontend"
else
WORKSPACE="/workspaces/frontend"
fi
if [ -z $(which hass) ]; then
echo "Installing Home Asstant core from dev."
python3 -m pip install --upgrade \
@@ -22,9 +16,9 @@ if [ -z $(which hass) ]; then
git+git://github.com/home-assistant/home-assistant.git@dev
fi
if [ ! -d "${WORKSPACE}/config" ]; then
if [ ! -d "/workspaces/frontend/config" ]; then
echo "Creating default configuration."
mkdir -p "${WORKSPACE}/config";
mkdir -p "/workspaces/frontend/config";
hass --script ensure_config -c config
echo "demo:
@@ -32,24 +26,24 @@ logger:
default: info
logs:
homeassistant.components.frontend: debug
" >> "${WORKSPACE}/config/configuration.yaml"
" >> /workspaces/frontend/config/configuration.yaml
if [ ! -z "${HASSIO}" ]; then
echo "
# frontend:
# development_repo: ${WORKSPACE}
# development_repo: /workspaces/frontend
hassio:
development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
else
echo "
frontend:
development_repo: ${WORKSPACE}
development_repo: /workspaces/frontend
# hassio:
# development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
fi
fi
hass -c "${WORKSPACE}/config"
hass -c /workspaces/frontend/config
+2 -2
View File
@@ -2,9 +2,9 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20210531.0",
version="20210911.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",
author_email="hello@home-assistant.io",
license="Apache-2.0",
+24 -3
View File
@@ -7,6 +7,7 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import "./ha-password-manager-polyfill";
import { property, state } from "lit/decorators";
import "../components/ha-form/ha-form";
import "../components/ha-markdown";
@@ -20,7 +21,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
type State = "loading" | "error" | "step";
class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@property() public authProvider?: AuthProvider;
@property({ attribute: false }) public authProvider?: AuthProvider;
@property() public clientId?: string;
@@ -37,7 +38,15 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@state() private _errorMessage?: string;
protected render() {
return html` <form>${this._renderForm()}</form> `;
return html`
<form>${this._renderForm()}</form>
<ha-password-manager-polyfill
.step=${this._step}
.stepData=${this._stepData}
@form-submitted=${this._handleSubmit}
@value-changed=${this._stepDataChanged}
></ha-password-manager-polyfill>
`;
}
protected firstUpdated(changedProps: PropertyValues) {
@@ -231,11 +240,17 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
await this.updateComplete;
// 100ms to give all the form elements time to initialize.
setTimeout(() => {
const form = this.shadowRoot!.querySelector("ha-form");
const form = this.renderRoot.querySelector("ha-form");
if (form) {
(form as any).focus();
}
}, 100);
setTimeout(() => {
this.renderRoot.querySelector(
"ha-password-manager-polyfill"
)!.boundingRect = this.getBoundingClientRect();
}, 500);
}
private _stepDataChanged(ev: CustomEvent) {
@@ -329,3 +344,9 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
}
}
customElements.define("ha-auth-flow", HaAuthFlow);
declare global {
interface HTMLElementTagNameMap {
"ha-auth-flow": HaAuthFlow;
}
}
+5 -19
View File
@@ -8,10 +8,6 @@ import {
AuthUrlSearchParams,
fetchAuthProviders,
} from "../data/auth";
import {
DiscoveryInformation,
fetchDiscoveryInformation,
} from "../data/discovery";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { registerServiceWorker } from "../util/register-service-worker";
import "./ha-auth-flow";
@@ -29,8 +25,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@state() private _authProviders?: AuthProvider[];
@state() private _discovery?: DiscoveryInformation;
constructor() {
super();
this.translationFragment = "page-authorize";
@@ -58,17 +52,14 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
// the name with a bold tag.
const loggingInWith = document.createElement("div");
loggingInWith.innerText = this.localize(
this._discovery?.location_name
? "ui.panel.page-authorize.logging_in_to_with"
: "ui.panel.page-authorize.logging_in_with",
"locationName",
"LOCATION",
"ui.panel.page-authorize.logging_in_with",
"authProviderName",
"NAME"
);
loggingInWith.innerHTML = loggingInWith.innerHTML
.replace("**LOCATION**", `<b>${this._discovery?.location_name}</b>`)
.replace("**NAME**", `<b>${this._authProvider!.name}</b>`);
loggingInWith.innerHTML = loggingInWith.innerHTML.replace(
"**NAME**",
`<b>${this._authProvider!.name}</b>`
);
const inactiveProviders = this._authProviders.filter(
(prv) => prv !== this._authProvider
@@ -108,7 +99,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetchAuthProviders();
this._fetchDiscoveryInfo();
if (matchMedia("(prefers-color-scheme: dark)").matches) {
applyThemesOnElement(
@@ -144,10 +134,6 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
}
}
private async _fetchDiscoveryInfo() {
this._discovery = await fetchDiscoveryInformation();
}
private async _fetchAuthProviders() {
// Fetch auth providers
try {
+110
View File
@@ -0,0 +1,110 @@
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { HaFormSchema } from "../components/ha-form/ha-form";
import { DataEntryFlowStep } from "../data/data_entry_flow";
declare global {
interface HTMLElementTagNameMap {
"ha-password-manager-polyfill": HaPasswordManagerPolyfill;
}
interface HASSDomEvents {
"form-submitted": undefined;
}
}
const ENABLED_HANDLERS = [
"homeassistant",
"legacy_api_password",
"command_line",
];
@customElement("ha-password-manager-polyfill")
export class HaPasswordManagerPolyfill extends LitElement {
@property({ attribute: false }) public step?: DataEntryFlowStep;
@property({ attribute: false }) public stepData: any;
@property({ attribute: false }) public boundingRect?: DOMRect;
protected createRenderRoot() {
// Add under document body so the element isn't placed inside any shadow roots
return document.body;
}
private get styles() {
return `
.password-manager-polyfill {
position: absolute;
top: ${this.boundingRect?.y || 148}px;
left: calc(50% - ${(this.boundingRect?.width || 360) / 2}px);
width: ${this.boundingRect?.width || 360}px;
opacity: 0;
z-index: -1;
}
.password-manager-polyfill input {
width: 100%;
height: 62px;
padding: 0;
border: 0;
}
.password-manager-polyfill input[type="submit"] {
width: 0;
height: 0;
}
`;
}
protected render(): TemplateResult {
if (
this.step &&
this.step.type === "form" &&
this.step.step_id === "init" &&
ENABLED_HANDLERS.includes(this.step.handler[0])
) {
return html`
<form
class="password-manager-polyfill"
aria-hidden="true"
@submit=${this._handleSubmit}
>
${this.step.data_schema.map((input) => this.render_input(input))}
<input type="submit" />
<style>
${this.styles}
</style>
</form>
`;
}
return html``;
}
private render_input(schema: HaFormSchema): TemplateResult | string {
const inputType = schema.name.includes("password") ? "password" : "text";
if (schema.type !== "string") {
return "";
}
return html`
<input
tabindex="-1"
.id=${schema.name}
.type=${inputType}
.value=${this.stepData[schema.name] || ""}
@input=${this._valueChanged}
/>
`;
}
private _handleSubmit(ev: Event) {
ev.preventDefault();
fireEvent(this, "form-submitted");
}
private _valueChanged(ev: Event) {
const target = ev.target! as HTMLInputElement;
this.stepData = { ...this.stepData, [target.id]: target.value };
fireEvent(this, "value-changed", {
value: this.stepData,
});
}
}
+63
View File
@@ -0,0 +1,63 @@
export const COLORS = [
"#44739e",
"#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",
];
export function getColorByIndex(index: number) {
return COLORS[index % COLORS.length];
}
+1 -1
View File
@@ -1,4 +1,4 @@
const luminosity = (rgb: [number, number, number]): number => {
export const luminosity = (rgb: [number, number, number]): number => {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
const lum: [number, number, number] = [0, 0, 0];
for (let i = 0; i < rgb.length; i++) {
+17 -64
View File
@@ -42,6 +42,7 @@ export const FIXED_DOMAIN_ICONS = {
remote: "hass:remote",
scene: "hass:palette",
script: "hass:script-text",
select: "hass:format-list-bulleted",
sensor: "hass:eye",
simple_alarm: "hass:bell",
sun: "hass:white-balance-sunny",
@@ -55,18 +56,31 @@ export const FIXED_DOMAIN_ICONS = {
};
export const FIXED_DEVICE_CLASS_ICONS = {
aqi: "hass:air-filter",
current: "hass:current-ac",
carbon_dioxide: "mdi:molecule-co2",
carbon_monoxide: "mdi:molecule-co",
energy: "hass:flash",
date: "hass:calendar",
energy: "hass:lightning-bolt",
gas: "hass:gas-cylinder",
humidity: "hass:water-percent",
illuminance: "hass:brightness-5",
nitrogen_dioxide: "mdi:molecule",
nitrogen_monoxide: "mdi:molecule",
nitrous_oxide: "mdi:molecule",
ozone: "mdi:molecule",
temperature: "hass:thermometer",
monetary: "mdi:cash",
pm25: "mdi:molecule",
pm1: "mdi:molecule",
pm10: "mdi:molecule",
pressure: "hass:gauge",
power: "hass:flash",
power_factor: "hass:angle-acute",
signal_strength: "hass:wifi",
sulphur_dioxide: "mdi:molecule",
timestamp: "hass:clock",
volatile_organic_compounds: "mdi:molecule",
voltage: "hass:sine-wave",
};
@@ -83,6 +97,7 @@ export const DOMAINS_WITH_CARD = [
"number",
"scene",
"script",
"select",
"timer",
"vacuum",
"water_heater",
@@ -121,6 +136,7 @@ export const DOMAINS_HIDE_MORE_INFO = [
"input_text",
"number",
"scene",
"select",
];
/** Domains that should have the history hidden in the more info dialog. */
@@ -153,66 +169,3 @@ export const UNIT_F = "°F";
/** Entity ID of the 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",
];
+6 -3
View File
@@ -26,6 +26,9 @@ function checkToLocaleStringSupportsOptions() {
return false;
}
export const toLocaleDateStringSupportsOptions = checkToLocaleDateStringSupportsOptions();
export const toLocaleTimeStringSupportsOptions = checkToLocaleTimeStringSupportsOptions();
export const toLocaleStringSupportsOptions = checkToLocaleStringSupportsOptions();
export const toLocaleDateStringSupportsOptions =
checkToLocaleDateStringSupportsOptions();
export const toLocaleTimeStringSupportsOptions =
checkToLocaleTimeStringSupportsOptions();
export const toLocaleStringSupportsOptions =
checkToLocaleStringSupportsOptions();
@@ -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,
};
};
+80 -16
View File
@@ -3,20 +3,11 @@ import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { toLocaleDateStringSupportsOptions } from "./check_options_support";
const formatDateMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
})
);
export const formatDate = toLocaleDateStringSupportsOptions
// Tuesday, August 10
export const formatDateWeekday = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "longDate");
formatDateWeekdayMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "dddd, MMMM D");
const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
@@ -26,7 +17,80 @@ const formatDateWeekdayMem = memoizeOne(
})
);
export const formatDateWeekday = toLocaleDateStringSupportsOptions
// August 10, 2021
export const formatDate = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateWeekdayMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
formatDateMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY");
const formatDateMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
})
);
// 10/08/2021
export const formatDateNumeric = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateNumericMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "M/D/YYYY");
const formatDateNumericMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "numeric",
day: "numeric",
})
);
// Aug 10
export const formatDateShort = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateShortMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMM D");
const formatDateShortMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
day: "numeric",
month: "short",
})
);
// August 2021
export const formatDateMonthYear = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMonthYearMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMMM YYYY");
const formatDateMonthYearMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
year: "numeric",
})
);
// August
export const formatDateMonth = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMonthMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "MMMM");
const formatDateMonthMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
})
);
// 2021
export const formatDateYear = toLocaleDateStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateYearMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "YYYY");
const formatDateYearMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
})
);
+27 -9
View File
@@ -4,39 +4,57 @@ import { FrontendLocaleData } from "../../data/translation";
import { toLocaleStringSupportsOptions } from "./check_options_support";
import { useAmPm } from "./use_am_pm";
// August 9, 2021, 8:23 AM
export const formatDateTime = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
const formatDateTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hour12: useAmPm(locale),
})
);
export const formatDateTime = toLocaleStringSupportsOptions
// August 9, 2021, 8:23:15 AM
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeMem(locale).format(dateObj)
formatDateTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
const formatDateTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: useAmPm(locale),
})
);
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
// 9/8/2021, 8:23 AM
export const formatDateTimeNumeric = toLocaleStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeWithSecondsMem(locale).format(dateObj)
formatDateTimeNumericMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
format(dateObj, "M/D/YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
const formatDateTimeNumericMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: useAmPm(locale),
})
);
+26 -26
View File
@@ -4,49 +4,49 @@ import { FrontendLocaleData } from "../../data/translation";
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
import { useAmPm } from "./use_am_pm";
const formatTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
hour: "numeric",
minute: "2-digit",
hour12: useAmPm(locale),
})
);
// 9:15 PM || 21:15
export const formatTime = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
const formatTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hour12: useAmPm(locale),
})
);
// 9:15:24 PM || 21:15:24
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
const formatTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
hour: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: useAmPm(locale),
})
);
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
const formatTimeWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
hour: "numeric",
minute: "2-digit",
hour12: useAmPm(locale),
})
);
// Tuesday 7:00 PM || Tuesday 19:00
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWeekdayMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
const formatTimeWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hour12: useAmPm(locale),
})
);

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