Compare commits

..
Author SHA1 Message Date
Ludeeus d1605ba196 Add move data disk 2021-09-16 15:15:57 +00:00
2240d019f5 Specify period when fetching statistics (#10040)
Co-authored-by: Bram Kragten <[email protected]>
2021-09-16 11:15:23 +02:00
Philip AllgaierandGitHub cb11c6b3ea Check for null action nodes before rendering (#10017) 2021-09-14 14:20:09 -07:00
Jaroslav HanslíkandGitHub 5893559951 New icon for "on" state of smoke device (#10013) 2021-09-13 12:50:57 +02:00
Paulus SchoutsenandGitHub 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íkandGitHub 6b6c38c2c8 Better icon for alarm panel state "armed night" (#10012) 2021-09-11 11:53:38 -07:00
Joakim SørensenandGitHub e55df73a91 Remove usage of discovery info (#10015) 2021-09-11 11:38:35 -07:00
Michael IrigoyenandGitHub 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 KistandGitHub aba96674f3 Use unicode ellipsis instead of 3 dots "..." (#9997) 2021-09-09 15:43:07 +00:00
Philip AllgaierandGitHub 5c3d85fc90 Consistent lower-case spelling of "optional" and "required" (#9990) 2021-09-09 15:42:42 +02:00
Philip AllgaierandGitHub 6486b7fd4c Adjust dev-tools wording: "Active listeners" (#9991) 2021-09-09 15:42:14 +02:00
Paulus SchoutsenandGitHub 5f3e980de0 Add gas unit error (#9981) 2021-09-07 14:49:08 -07:00
Philip AllgaierandGitHub d0edbec5fb Copy resize observer to "non-input" number entity row (#9973) 2021-09-07 17:25:08 +02:00
Ville SkyttäandGitHub 5d46963e8a Add date device class (#9983) 2021-09-07 17:24:10 +02:00
Philip AllgaierandGitHub 321f441b63 Handle unavailable vacuums in more-info (#9974) 2021-09-07 10:14:05 +02:00
Paulus SchoutsenandGitHub d55bade070 Small tweaks for the create automation from blueprint screen (#9980) 2021-09-07 09:40:25 +02:00
Ville SkyttäandGitHub 6ba6b821f5 Use SENSOR_DEVICE_CLASS_* constants more (#9982) 2021-09-07 09:21:51 +02:00
b3dedae115 Add ResizeObserver to EntityRow (#9837)
Co-authored-by: Bram Kragten <[email protected]>
2021-09-06 18:34:10 +02:00
Philip AllgaierandGitHub 5a1070c30f Prevent darkMode overwrite by frontend_default_dark_theme (#9519) 2021-09-06 13:13:01 +02:00
Bram KragtenandGitHub 40664997e1 Use polyfill from toggleAttribute (#9969) 2021-09-06 11:48:08 +02:00
Philip AllgaierandGitHub c6e83cb7c0 Add state_color support to entity card (#9617) 2021-09-06 11:26:16 +02:00
Philip AllgaierandGitHub e7e27e794c Add refresh button to history panel (#9958) 2021-09-06 09:19:58 +00:00
Sven NaumannandGitHub 1073dbe6ab number slider: change column width check from 350px to 300px (#8310) 2021-09-06 11:01:28 +02:00
2bd9b5a015 Prevent index access errors in entity quick bar (#9964)
Co-authored-by: Bram Kragten <[email protected]>
2021-09-06 09:01:18 +00:00
Philip AllgaierandGitHub bc09febd2c Use integration manifest for service documentation URL (#9960) 2021-09-06 08:58:05 +00:00
Philip AllgaierandGitHub b2a87c90a2 Proper editor enum validation of timestamp formats (#9965) 2021-09-06 08:42:42 +00:00
Philip AllgaierandGitHub d6dbbcb0de Use dynamic struct validation for entities card rows (#9962) 2021-09-06 10:27:29 +02:00
Philip AllgaierandGitHub 9ccb5360b3 Add icon to attribute row validation (#9963) 2021-09-06 10:26:11 +02:00
Philip AllgaierandGitHub 0187c4faff Ensure calender follows time format locale (#9966) 2021-09-06 10:22:46 +02:00
Philip AllgaierandGitHub 605172a0bc Align internal and fecha date/time formatting (#9380) 2021-09-06 10:22:01 +02:00
Bram KragtenandGitHub 8565a0d911 Remove float value in float form when emptied (#9947) 2021-09-03 20:07:22 +02:00
Bram KragtenandGitHub 61c8d23a7e Bunch of minor non breaking dep bumps (#9945) 2021-09-03 10:55:10 -07:00
Joakim SørensenandGitHub 5e3487ed59 Don't build wheels for alpine 3.13 (#9944) 2021-09-03 11:56:30 +02:00
Joakim SørensenandGitHub d5a161769c Fix removeBackup function after 2021.9 (#9938) 2021-09-02 22:13:11 +02:00
Bram KragtenandGitHub 1692f9c2dd Change message to info alert (#9930) 2021-09-02 00:40:51 +02:00
Bram KragtenandGitHub 0cbac8bb44 Polyfill Array.flat (#9917) 2021-09-01 16:23:41 +02:00
Bram KragtenandGitHub 35a81e7f11 Correct badge warning (and use new styling) (#9926) 2021-09-01 16:23:29 +02:00
Paulus SchoutsenandGitHub ac64d293e7 Sort tags in trigger (#9921) 2021-08-31 21:30:35 -07:00
Bram KragtenandGitHub 708b8787c5 Bump round slider (#9301) 2021-08-31 08:59:32 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 324658a36b Fix energy graph when sum is 0 (#9914) 2021-08-30 20:06:58 +00:00
Joakim SørensenandGitHub 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ørensenandGitHub 2ab0e40952 Break overflow in ha-alert (#9885) 2021-08-30 18:08:16 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 6e38f5accf Align entity errors (#9904) 2021-08-30 08:38:33 +00:00
Bram KragtenandGitHub 7c952d92bf Fix hat-graph-margin (#9898) 2021-08-28 11:12:02 +00:00
Bram KragtenandGitHub 2fae0d2d95 Fix tracing graph on iOS (#9897) 2021-08-28 01:04:52 +02:00
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 <[email protected]>

Co-authored-by: Bram Kragten <[email protected]>
2021-08-27 16:45:05 +02:00
719f9c28af Fix missing alarm state in translation: vacation (#9893)
Co-authored-by: Joakim Sørensen <[email protected]>
2021-08-27 13:24:24 +02:00
Joakim SørensenandGitHub 035d621109 Fix delete backup endpoint (#9890) 2021-08-27 12:26:23 +02:00
Joakim SørensenandGitHub 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
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 <[email protected]>
2021-08-25 10:55:16 -07:00
900efe8a36 Add direct link to zwave_js device's device DB page (#9797)
Co-authored-by: Charles Garwood <[email protected]>
Co-authored-by: Paulus Schoutsen <[email protected]>
2021-08-25 17:53:30 +00:00
Paulus SchoutsenandGitHub 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 GarwoodandGitHub b15684bcbd Include config entry title in delete confirmation prompt (#9866) 2021-08-25 10:36:39 -07:00
Joakim SørensenandGitHub a93222dbb2 Show history graph for sensors with state_class and not unit (#9879) 2021-08-25 13:41:18 +02:00
Joakim SørensenandGitHub 20744e90a0 Use ha-alert in lovelace alerts/warnings (#9880) 2021-08-25 11:24:04 +02:00
Bram KragtenandGitHub 32777b4259 Add value column prop to ha data table (#9824) 2021-08-25 11:20:35 +02:00
Paulus SchoutsenandGitHub 271120999c Center nodes in narrow mode (#9878) 2021-08-25 10:22:37 +02:00
Joakim SørensenandGitHub 68fe13a67d Use ha-alert in the energy validation results (#9876) 2021-08-24 19:22:40 +02:00
Joakim SørensenandGitHub f3606014c6 Add ha-alert component (#9874) 2021-08-24 09:44:30 -07:00
Joakim SørensenandGitHub efbf4482b2 Build wheels for Alpine 3.14 (#9875) 2021-08-24 11:20:57 +02:00
Jc2kandGitHub 21a3b4f8e2 Add volatile_organic_compounds device class (#9871) 2021-08-23 11:43:46 +02:00
Paulus SchoutsenandGitHub de23b2d046 Do not show domain if equal to title with different casing (#9870) 2021-08-22 23:47:09 -07:00
Paulus SchoutsenandGitHub bd8f436c1d Do not render info tooltip if we have an error (#9869) 2021-08-22 23:47:03 -07:00
Bram KragtenandGitHub e963735dba Cleanup mjpeg stream when disconnected (+ bump Lit) (#9868) 2021-08-22 22:40:37 -07:00
46c981103d Only update cameras when they are visible (#9690)
Co-authored-by: Bram Kragten <[email protected]>
2021-08-22 22:55:56 +00:00
J. Nick KostonandGitHub f6d02d8fc6 Cache the camera url without the width and height added (#9778) 2021-08-22 19:17:48 +02:00
J. Nick KostonandGitHub e08f691510 Trigger a scan of USB devices when loading integrations (#9860) 2021-08-22 19:14:14 +02:00
Franck NijhofandGitHub af9199aaff Add missing BYN currency (#9863) 2021-08-22 14:40:19 +02:00
Charles GarwoodandGitHub 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
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 <[email protected]>
2021-08-18 13:16:12 -07:00
Paulus SchoutsenandGitHub 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
9e3d339ec5 Fix typo in gas paragraph (#9836)
* Fix typo in gas paragraph

* Update src/translations/en.json

Co-authored-by: Paulus Schoutsen <[email protected]>
2021-08-18 04:31:00 +00:00
Bram KragtenandGitHub fb97a98b97 Add view_layout to card structs, fix button entity row struct (#9834) 2021-08-17 21:27:15 -07:00
Joakim SørensenandGitHub 72773f3bc8 Add disabled by column to entity datatable (#9799) 2021-08-17 21:26:21 -07:00
Franck NijhofandGitHub b0fd93e0c3 Add default icon for gas device class (#9830) 2021-08-17 11:37:53 +02:00
MichaelandGitHub 7aa2ec78f2 Add icons for the device_class 'update' (#9711) 2021-08-17 01:31:29 +02:00
Jc2kandGitHub 047e856a61 Add new air quality device classes to frontend (#9829) 2021-08-16 23:34:36 +02:00
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 <[email protected]>
2021-08-15 20:45:24 -07:00
Bram KragtenandGitHub e0d23ee6cf Make sure idle and random color dont collide (#9808) 2021-08-15 20:45:07 -07:00
Bram KragtenandGitHub 7a35f46370 Set suggested min max to min and max (#9805) 2021-08-15 20:44:42 -07:00
Bram KragtenandGitHub 4a4465efb6 FIx device graph not rendering right when changing period (#9806) 2021-08-15 20:44:12 -07:00
Bram KragtenandGitHub 3a112531cc Make energy period selector less wide for mobile (#9814) 2021-08-15 20:43:35 -07:00
Bram KragtenandGitHub 456209dded Don't start video after the element was removed from DOM (#9813) 2021-08-15 20:43:00 -07:00
Bram KragtenandGitHub 2556b0d157 Improve iOS 12 check (#9816) 2021-08-15 20:42:39 -07:00
Bram KragtenandGitHub 1e8903fd76 There are 5 steps now (#9822) 2021-08-15 20:42:20 -07:00
Bram KragtenandGitHub ad9f18c231 Keep datasets hidden after data update (#9823) 2021-08-15 20:36:15 -07:00
Bram KragtenandGitHub 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 KragtenandGitHub 3479fb9d94 Add gas to energy dashboard (#9787) 2021-08-13 10:39:20 -07:00
Paulus SchoutsenandGitHub 304bd002ae Netto -> Net (#9798) 2021-08-13 08:48:23 +02:00
Bram KragtenandGitHub 5dad18c85f Make time inputs the same through the UI (#9766) 2021-08-12 22:52:26 +02:00
Bram KragtenandGitHub 19e4c0657a Add battery to energy dashboard (#9757) 2021-08-12 08:40:21 -07:00
Bram KragtenandGitHub 44548fdc33 Fix tracing graph (#9782) 2021-08-12 15:59:57 +02:00
Bram KragtenandGitHub d8929074b5 Use correct vars for gauge colors (#9727) 2021-08-12 15:59:42 +02:00
Bram KragtenandGitHub e11532ae92 Update translations (#9784) 2021-08-12 12:38:52 +02:00
eff48acdf4 Rename snapshot -> backup (#9393)
Co-authored-by: Bram Kragten <[email protected]>
2021-08-12 11:56:13 +02:00
Onur-dandGitHub a9850f9641 Add max-width for Logos to 100% (#9779) 2021-08-11 17:19:12 +02:00
Joakim SørensenandGitHub aab0b8a3ce Request darktheme optimized brand images if dark theme is used (#9777) 2021-08-11 15:15:12 +02:00
Philip AllgaierandGitHub b12e062d94 Fix "wan't" typos in energy panel (#9775) 2021-08-11 09:32:32 +02:00
b36e342f15 Pass the width and height when requesting camera images (#9683)
Co-authored-by: Paulus Schoutsen <[email protected]>
2021-08-10 21:42:37 -07:00
Bram KragtenandGitHub f686816c86 Cleanup of tracing graph (#9564) 2021-08-11 01:23:07 +02:00
Bram KragtenandGitHub dc50e54afc Add period selection to energy dashboard (#9756) 2021-08-11 01:22:27 +02:00
Paulus SchoutsenandGitHub 3897e3d452 Fix language string (#9767) 2021-08-10 21:22:38 +02:00
Bram KragtenandGitHub 2557b03b11 Catch failing to fetch forecasts (#9765) 2021-08-10 19:47:09 +02:00
Paulus SchoutsenandGitHub 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 KragtenandGitHub afd1a68c62 Change some wording (#9742) 2021-08-09 20:43:54 +02:00
Bram KragtenandGitHub dbcf1cb907 Group solar forecasts by hour (#9728) 2021-08-09 17:56:25 +02:00
Paulus SchoutsenandGitHub 9ca64f9789 Show reconnecting when not connected and auto-connect is on (#9738) 2021-08-07 11:46:26 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 7fcea16c6b Increase font size tooltips + position then static (#9713) 2021-08-04 09:19:38 -07:00
Bram KragtenandGitHub 028b799d2c Default energy collection key should not be defined (#9710) 2021-08-04 08:25:42 -07:00
Bram KragtenandGitHub 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 SchoutsenandGitHub 80b330ad7b Specify YARN version on netlify (#9705) 2021-08-03 21:55:15 -07:00
Bram KragtenandPaulus Schoutsen d929e1c134 Add support for statistics card to demo (#9703) 2021-08-03 15:16:21 -07:00
Bram KragtenandGitHub 5e40dcdc38 Add support for statistics card to demo (#9703) 2021-08-03 15:16:08 -07:00
Paulus SchoutsenandGitHub 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 SchoutsenandGitHub 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 KragtenandGitHub 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 KragtenandGitHub 5ba24211e2 Add messages when no data (#9700)
* Add messages when no data

* 2 hours

* comment
2021-08-03 23:22:32 +02:00
Bram KragtenandGitHub d699647418 Fix calculate sum growth + refreshing too often (#9699) 2021-08-03 23:19:26 +02:00
b246502cb6 Energy demo (#9698)
Co-authored-by: Paulus Schoutsen <[email protected]>
2021-08-03 09:50:31 -07:00
Bram KragtenandGitHub 9b33ead8aa Final energy tweaks (#9694) 2021-08-03 08:54:10 -07:00
Milan MeulemansandGitHub 32e8c1dc6d Remove energy currency translation (#9695) 2021-08-03 11:00:21 +00:00
Paulus SchoutsenandGitHub 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 KragtenandGitHub 1c097a669d Add currency to onboarding (#9691) 2021-08-02 21:02:57 +00:00
Bram KragtenandGitHub 4e1497c5da Change layout of period selector (#9688) 2021-08-02 13:57:59 -07:00
Bram KragtenandGitHub 49d426675f Masonry (#9689) 2021-08-02 13:57:39 -07:00
Paulus SchoutsenandGitHub 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 SchoutsenandGitHub 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
50bd5ee8f7 Add my support for energy (#9681)
Co-authored-by: Bram Kragten <[email protected]>
2021-08-02 09:50:47 -07:00
Joakim SørensenandGitHub 285f3fe330 Fix text color for hass-tabs-subpage with narrow (#9680) 2021-08-02 09:50:24 -07:00
Bram KragtenandGitHub 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 SchoutsenandGitHub 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 KragtenandGitHub 884e323288 Make clear it is showing today (#9679) 2021-08-02 07:22:36 -07:00
Bram KragtenandGitHub 78b799dd05 Add cost stat ids (#9678) 2021-08-02 07:22:17 -07:00
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 <[email protected]>
2021-08-02 12:36:13 +00:00
Paulus SchoutsenandGitHub 481a79e311 Allow specifying collection keys so energy dashboard has own (#9674) 2021-08-02 14:23:15 +02:00
Paulus SchoutsenandGitHub f19f2ff321 Add tooltips to the gauges (#9676) 2021-08-02 14:21:41 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 2adbb47373 Refresh stats at 20 minutes past the hour every hour (#9667) 2021-08-01 21:24:41 +00:00
Bram KragtenandGitHub 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 KragtenandGitHub 044d6a15d9 Add view type selector to view editor (#9671) 2021-08-01 22:55:51 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 6fd85e043b Sidebar view: Allow to move card position from UI (#9669) 2021-08-01 18:17:34 +02:00
Bram KragtenandGitHub e07ac52356 Add UOM to stats chart, fix coloring of bands (#9665) 2021-07-31 20:36:27 +02:00
0f16ba9325 Speed up data loading and allow embedding individual energy cards (#9660)
Co-authored-by: Bram Kragten <[email protected]>
2021-07-31 18:33:41 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 2982adbfa7 Fix return compensation not being negative (#9654) 2021-07-30 12:57:27 -07:00
Bram KragtenandGitHub 5147dff670 Sidebar view: Move all cards to 1 column on mobile (#9656) 2021-07-30 12:57:01 -07:00
Bram KragtenandGitHub 2cdf78c504 Add grid neutrality gauge (#9655) 2021-07-30 12:55:58 -07:00
Bram KragtenandGitHub cfad45b7c2 fix and finish statistics card (#9658) 2021-07-30 12:52:38 -07:00
Bram KragtenandGitHub 5234e9bce5 still round numbers when not formatting them (#9653) 2021-07-30 17:36:31 +02:00
Bram KragtenandGitHub 0ed5454d02 fix self consumed solar (#9651) 2021-07-30 12:28:47 +02:00
Thomas LovénandGitHub 03080973be Add needle option to ha-gauge and gauge card (#9637) 2021-07-30 10:51:21 +02:00
Paulus SchoutsenandGitHub 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 KragtenandGitHub d4cbdab4a3 Fix energy calculations (#9647)
* Fix calculations

* max.. not min...
2021-07-29 18:29:49 +00:00
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 <[email protected]>

* fix typos

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

* Prettier

Co-authored-by: Paulus Schoutsen <[email protected]>
2021-07-29 20:26:22 +02:00
Bram KragtenandGitHub 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 EhrhartandGitHub 6e7af18494 Add support for DEVICE_CLASS_MONETARY (#9640) 2021-07-29 15:20:12 +00:00
Paulus SchoutsenandGitHub 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 SchoutsenandGitHub 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 SchoutsenandGitHub 469faf509b Remove the energy summary card since it's already in other cards (#9635) 2021-07-28 10:46:59 -07:00
Paulus SchoutsenandGitHub 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 KragtenandGitHub 9c31b749d7 Format monetary sensor as currency (#9633) 2021-07-28 09:34:03 -07:00
Paulus SchoutsenandGitHub 521d5df064 Show solar forecast even if no prod stats available (#9632) 2021-07-28 09:20:31 -07:00
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 <[email protected]>
2021-07-28 09:20:06 -07:00
2e04a55d5c Add statistics card to card picker (#9631)
Co-authored-by: Zack Barett <[email protected]>
2021-07-28 08:41:18 -07:00
Bram KragtenandGitHub 0d9d0aa18b Add legend to electricity graph, fix text color in table (#9629) 2021-07-28 08:40:43 -07:00
Bram KragtenandGitHub f8dd1795bc Add animation to distribution (#9625) 2021-07-28 08:39:12 -07:00
Bram KragtenandGitHub 1d7007584c Use currency from core config (#9628) 2021-07-28 08:38:04 -07:00
8cd9f891fb Add remove failed node support to Z-Wave JS devices (#9560)
Co-authored-by: Bram Kragten <[email protected]>
2021-07-28 13:38:50 +00:00
6ab0f1db57 Add currency core configuration (#9620)
Co-authored-by: Joakim Sørensen <[email protected]>
Co-authored-by: Paulus Schoutsen <[email protected]>
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 GarwoodandGitHub e12b194d41 Add Z-Wave JS Heal Node wizard (#9562) 2021-07-28 00:16:19 +02:00
Michelle FuchsandGitHub 07d7fa26fe Show value of 'Before'-time in automation editor #9142 (#9598) 2021-07-28 00:13:38 +02:00
Bram KragtenandGitHub 73b9b87ef3 Update energy dashboard (#9624) 2021-07-27 13:57:09 -07:00
Bram KragtenandGitHub 0c0091375c Fix grid onboarding (#9621) 2021-07-27 21:34:43 +02:00
Bram KragtenandGitHub 21a29ed3a5 Statistic sums requires at least 2 values (#9616) 2021-07-26 22:47:50 +00:00
Bram KragtenandGitHub a6312f4279 Fix sidebar view edit mode (#9615) 2021-07-26 22:11:34 +00:00
Bram KragtenandGitHub f459abdf85 Fix statistics graph (#9607) 2021-07-26 15:03:31 -07:00
Bram KragtenandGitHub 586fa1d0f0 Fix zoom setting in maps card (#9613)
Fixes #9611
2021-07-26 15:02:50 -07:00
Bram KragtenandGitHub bf4192a1f0 Bump Vaadin elements (#9609) 2021-07-26 15:02:23 -07:00
Bram KragtenandGitHub ac31eedf65 Bump material elements (#9610) 2021-07-26 15:02:01 -07:00
Bram KragtenandGitHub b05dc5141c Fix chart tooltip footer always rendering (#9614) 2021-07-26 15:01:32 -07:00
Raman GuptaandGitHub 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 GuptaandGitHub 982c940381 Try to fix download of zwave_js logs (#9612) 2021-07-26 23:26:22 +02:00
Bram KragtenandGitHub a7a8aaa887 Merge pull request #9608 from home-assistant/dev
20210726.0
2021-07-26 23:04:05 +02:00
Philip AllgaierandGitHub 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 KragtenandGitHub a432cf8405 Fix selected icon of picker elements (#9606) 2021-07-26 22:17:29 +02:00
9dd6b3b72d Add Energy panel (#9445)
Co-authored-by: Paulus Schoutsen <[email protected]>
2021-07-26 09:57:59 -07:00
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 <[email protected]>

* Prettier

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

* Remove formatting from patch

* Cleanup

* Fix Vaadin

* Fix typing
2021-07-16 01:23:09 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 1078bb4287 Add statistics-graph-card (#9479)
* Add `statistics-graph-card`

* Make variable names clearer
2021-07-16 01:16:02 +02:00
Charles GarwoodandGitHub daeed06e70 Clean up Z-Wave JS dialog CSS (#9561) 2021-07-16 01:13:00 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub cc81239b9d Add struct for fire-dom-event action (#9556) 2021-07-15 12:08:33 +02:00
Bram KragtenandGitHub e797c01761 Update lint rules (#9563)
Updated deps

Added `unused-imports`, prefer arrow, and import order
2021-07-15 12:08:04 +02:00
Charles GarwoodandGitHub 12f7366968 Replace home ID with config entry title in Z-Wave device Info (#9488) 2021-07-13 18:05:22 +02:00
Bram KragtenandGitHub b7fd7abe85 increase memory demo action (#9553) 2021-07-13 10:15:59 +02:00
a5e1f3d165 bump hls.js (#9546)
Co-authored-by: Bram Kragten <[email protected]>
2021-07-13 09:00:51 +02:00
Joakim SørensenandGitHub 212d047ada Place Z-Wave JS stages in grid (#9548) 2021-07-12 12:44:07 -04:00
Bram KragtenandGitHub 2e16127fde Inline mdi icons with babel plugin + bump build deps (#9498) 2021-07-12 09:26:07 -07:00
Charles GarwoodandGitHub e8b53a619d Show parameter number on ZWaveJS device config panel (#9494) 2021-07-12 09:41:54 +02:00
df1ca1fd96 HA frontend change for alarm panel vacation mode (#8326)
Co-authored-by: Bram Kragten <[email protected]>
2021-07-08 16:21:09 +02:00
8f85132d48 Download translations on release (#9530)
Co-authored-by: Joakim Sørensen <[email protected]>
2021-07-08 13:04:38 +00:00
349144599c Change text on Yaml configuration reloading (#9529)
Co-authored-by: Bram Kragten <[email protected]>
2021-07-08 13:04:17 +00:00
979093923b Colorize trace paths for choose without explicit default case (#9527)
Co-authored-by: Bram Kragten <[email protected]>
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 KragtenandGitHub 6701c4c371 Dedupe dependencies (#9523)
+ yarn bump + CI check for duplicate dependencies
2021-07-07 17:31:37 +02:00
Bram KragtenandGitHub fc6e459c09 Upgrade to yarn 2 (#9500) 2021-07-07 16:33:24 +02:00
Philip AllgaierandGitHub 9e28b3447e Fixed "ID" spelling in trigger field label (#9518) 2021-07-07 11:07:57 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 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 KragtenandGitHub b4bbe63f0f Fix device trigger clearing trigger id (#9511) 2021-07-06 11:53:21 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 4b9487183b Add tracing to scripts (#9486) 2021-07-06 10:46:51 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 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ørensenandGitHub 30d6c68908 Fix writing supervisor entrypoint (#9489) 2021-07-01 11:34:22 +02:00
Bram KragtenandGitHub dc781da93a Use ES5 build for Supervisor on Safari 12 and below (#9485) 2021-07-01 09:27:02 +02:00
Bram KragtenandGitHub 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ørensenandGitHub be29828454 Change path for codespaces (#9484) 2021-06-30 15:58:01 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 8141f78a92 Use ES5 build for Safari 12 and below (#9482) 2021-06-30 12:02:01 +02:00
Joakim SørensenandGitHub be244b3d00 Rename hassos -> haos (#9477) 2021-06-30 12:00:33 +02:00
Bram KragtenandGitHub 805f5ff9b6 Recreate columns if cards change (#9480) 2021-06-30 11:52:36 +02:00
Bram KragtenandGitHub 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ørensenandGitHub ed4052365c Allow placeholders in config and option flows (#9314) 2021-06-30 01:09:18 +02:00
Joakim SørensenandGitHub 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 GarwoodandGitHub 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
db37dffdbb Fixed Logbook Card/Panel/Dialog Incorrect Entires for input_datetime Entities (#9399)
Co-authored-by: Bram Kragten <[email protected]>
2021-06-30 00:32:19 +02:00
Bram KragtenandGitHub 13cab7e301 Fix logbook card (#9476) 2021-06-29 18:32:51 +02:00
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 <[email protected]>
2021-06-29 11:14:50 -04:00
a3d1a3566d Fix ha-hls-player cleanup for lit 2 (#9388)
Co-authored-by: Bram Kragten <[email protected]>
2021-06-29 15:44:18 +02:00
Philip AllgaierandGitHub ba0be927ed Set min value for history graph card rendered hours (#9464) 2021-06-29 15:01:42 +02:00
Philip AllgaierandGitHub 4260606267 Set minimum = 1 hours to show (#9466) 2021-06-29 15:01:18 +02:00
Philip AllgaierandGitHub 4665db4f27 Fix integration card rename dialog logic (#9467) 2021-06-29 15:00:27 +02:00
Franck NijhofandGitHub 43503ba085 Fix number entity row availability when state unknown (#9475) 2021-06-29 14:57:39 +02:00
Joakim SørensenandGitHub 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 NijhofandGitHub 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
rianadonandGitHub a4aba93d57 Add input elements to login page for password managers (#9369) 2021-06-24 23:14:36 +02:00
d93db16963 Add button for zwave_js options flow (#9001)
Co-authored-by: Joakim Sørensen <[email protected]>
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 KragtenandGitHub 9a4a1cb4ec Fix charts tooltips and legends (#9448) 2021-06-21 10:38:50 +02:00
202d6957bc Allow clearing values in optional selects (#9442)
Co-authored-by: Bram Kragten <[email protected]>
2021-06-21 09:41:38 +02:00
Joakim SørensenandGitHub 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
c1d571de42 Add Select entity (#9422)
Co-authored-by: Bram Kragten <[email protected]>
2021-06-19 13:26:19 +02:00
GitHub Action cecb66451c Translation update 2021-06-19 00:48:32 +00:00
Bram KragtenandGitHub 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 GuptaandGitHub ce3c8f264d Update zwave_js log subscription to handle core changes (#9262) 2021-06-17 17:19:18 +02:00
Bram KragtenandGitHub 9fbd594f37 Check if /proc/version exists (#9438) 2021-06-17 15:23:20 +02:00
Bram KragtenandGitHub 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ørensenandGitHub 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 GuptaandGitHub 5deb570fdf Add button to download logs from zwave_js logs page (#9395) 2021-06-16 00:02:40 +02:00
Joakim SørensenandGitHub 915c46f144 Fix add-on configuration validation (#9424) 2021-06-15 21:00:28 +02:00
Philip AllgaierandGitHub 30d6c5eaf3 Gracefully handle logbook retrieval errors (#9377) 2021-06-15 19:54:18 +02:00
Philip AllgaierandGitHub 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 AllgaierandGitHub 0e3eed0563 Fix supervisor text "error_addon_not_started" (#9412) 2021-06-15 15:38:14 +02:00
Joakim SørensenandGitHub 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 KragtenandGitHub 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
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 <[email protected]>
2021-06-10 14:51:24 +02:00
Bram KragtenandGitHub 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
e9b1b3d853 Fix issues with restoring snapshot during onboarding (#9385)
Co-authored-by: Bram Kragten <[email protected]>
2021-06-08 17:57:53 +02:00
8a33d174d7 FIx selecting service/url path action after choosing default action (#9376)
Co-authored-by: Joakim Sørensen <[email protected]>
2021-06-08 14:24:58 +02:00
Joakim SørensenandGitHub 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
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 <[email protected]>
2021-06-07 10:45:20 +02:00
c68b76e2da Add hardware dialog (#9348)
Co-authored-by: Bram Kragten <[email protected]>
2021-06-07 10:16:33 +02:00
Joakim SørensenandGitHub 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]andGitHub 299c863f49 Bump ws from 6.2.1 to 6.2.2 (#9372) 2021-06-05 23:52:13 +02:00
Bram KragtenandGitHub 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 AdlerandGitHub 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 KragtenandGitHub 8666e6baae Merge pull request #9358 from home-assistant/dev 2021-06-03 23:12:30 +02:00
Bram KragtenandGitHub 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 KragtenandGitHub 5418474f64 Polyfill globalThis in latest build (#9352) 2021-06-03 22:50:33 +02:00
Philip AllgaierandGitHub 8836ba6ceb Pick the correct backend-selected active theme (#9357) 2021-06-03 22:48:52 +02:00
Bram KragtenandGitHub 509c5b497a Disable babel compact option (#9335) 2021-06-03 12:34:30 -07:00
Joakim SørensenandGitHub e00bcc9f48 Better exit navigation for my-ingress (#9342) 2021-06-03 10:01:12 -07:00
Joakim SørensenandGitHub 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 KragtenandGitHub 68bc549d6a Use HLS light build (#9338)
* Use HLS light build

* Bump hls, backBufferLength
2021-06-02 18:34:18 +02:00
Bram KragtenandGitHub 9c64eafc21 Fix ZHA visualization (#9337) 2021-06-02 18:33:55 +02:00
Bram KragtenandGitHub 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
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 <[email protected]>

* Further changes from code review

Co-authored-by: Bram Kragten <[email protected]>
2021-06-02 09:59:22 +02:00
GitHub Action e49664bad3 Translation update 2021-06-02 04:10:37 +00:00
Bram KragtenandGitHub 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
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 <[email protected]>

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

Co-authored-by: Bram Kragten <[email protected]>
2021-06-01 12:22:25 -07:00
Bram KragtenandGitHub acd5e1c081 Fix back button too wide on mobile (#9333) 2021-06-01 12:12:00 -07:00
Bram KragtenandGitHub cc1c5e45b2 Display error when enabling/disabling config entries (#9325) 2021-06-01 21:03:00 +02:00
Bram KragtenandGitHub 038199c447 Change the type of debounce, use arrow functions (#9328) 2021-06-01 11:53:45 -07:00
Bram KragtenandGitHub 8a1eab7ceb Cleanup virtualizer styles (#9327) 2021-06-01 11:51:30 -07:00
bc5bd35448 Filter adapters with information from the Supervisor (#9322)
Co-authored-by: Bram Kragten <[email protected]>
2021-06-01 20:12:54 +02:00
Bram KragtenandGitHub 1795fd56b7 Don't rotate chart axis labels (#9329) 2021-06-01 10:28:03 -07:00
Bram KragtenandGitHub 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
rianadonandGitHub 797f60d725 Show pressure units in weather details card (#9295) 2021-06-01 11:40:04 +02:00
Bram KragtenandGitHub 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 SchoutsenandGitHub 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 SchoutsenandGitHub d8bab6aba9 Add support for disable polling system option (#9316) 2021-05-31 15:40:50 -07:00
Bram KragtenandGitHub a930e2dc75 Fix store auth disappearing (#9312) 2021-05-31 15:35:51 -07:00
J. Nick KostonandGitHub 2eb35668fa Seperate addresses in network configuration (#9319) 2021-05-31 15:31:33 -07:00
GitHub Action 07f4e5ac5c Translation update 2021-05-31 03:47:12 +00:00
Paulus SchoutsenandGitHub 1533c22d5c Merge pull request #9310 from home-assistant/dev 2021-05-30 20:30:44 -07:00
Paulus Schoutsen db82a90414 Bumped version to 20210531.0 2021-05-30 20:17:09 -07:00
Bram KragtenandGitHub 51a693badf Convert ha-store-auth-card to Lit/TS/ha-card (#9300) 2021-05-30 20:16:45 -07:00
Bram KragtenandGitHub 2aa8f5b352 Dev states: replace pattern in word by wildcard search (#9288) 2021-05-30 20:11:53 -07:00
Bram KragtenandGitHub 93b3b8f985 Fix editor structs (#9286) 2021-05-30 20:08:46 -07:00
Bram KragtenandGitHub 92c8bd80b5 Catch translation errors (#9299) 2021-05-30 17:02:03 +02:00
Philip AllgaierandGitHub 528af0157d Move entity attribution out of attribute expansion panel (#9296) 2021-05-30 16:06:22 +02:00
Bram Kragten 10a77b6278 Update translations 2021-05-30 16:02:03 +02:00
GitHub Action 03bbf6a582 Translation update 2021-05-30 03:38:49 +00:00
GitHub Action 63fcb649d2 Translation update 2021-05-29 03:21:09 +00:00
Bram KragtenandGitHub 4f60a92b92 Fix default themes (#9290)
* Fix default themes

* Simplify pick theme row
2021-05-28 20:02:28 -07:00
Bram KragtenandGitHub 0419c1a41f Fix icon loading (#9289) 2021-05-28 19:54:28 -07:00
2d5ae78521 Add selection to snapshot table for mass deletion (#9284)
Co-authored-by: Bram Kragten <[email protected]>
2021-05-28 15:49:40 +02:00
Joakim SørensenandGitHub 959134df02 Better secrets support in add-on configuration (#9275) 2021-05-28 14:37:16 +02:00
Bram KragtenandGitHub cf03f103ab Merge pull request #9285 from home-assistant/dev 2021-05-28 12:45:21 +02:00
Bram Kragten a9f9fc4ce2 Bumped version to 20210528.0 2021-05-28 12:26:38 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cfb370a3c8 Bump dns-packet from 1.3.1 to 1.3.4 (#9281)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-28 12:25:46 +02:00
Bram KragtenandGitHub 353435c8d5 Fix icon db loading (#9280) 2021-05-28 11:54:20 +02:00
Bram KragtenandGitHub c8c85d096b Show less ticks in charts (#9279) 2021-05-28 10:16:37 +02:00
GitHub Action 19c9c8f227 Translation update 2021-05-28 03:03:41 +00:00
Bram KragtenandGitHub 6ea2a29eea Hide attribute measurement and editable attributes (#9272) 2021-05-27 11:48:27 +02:00
Joakim SørensenandGitHub 59f3f819a6 Revert name change from selectedTheme to selectedThemeSettings (#9273) 2021-05-27 10:21:58 +02:00
GitHub Action 93e8f52880 Translation update 2021-05-27 02:45:51 +00:00
Joakim SørensenandGitHub 02810efcc4 Replace Hass_io_ prefix for snapshot downloads (#9270) 2021-05-26 21:56:27 +02:00
Bram KragtenandGitHub 4a8a7c997e Merge pull request #9267 from home-assistant/dev 2021-05-26 17:33:32 +02:00
Bram KragtenandGitHub 4b9be7ce16 Fix entity filtering in dev states (#9268) 2021-05-26 17:27:45 +02:00
Bram Kragten f3ec09e480 Bumped version to 20210526.0 2021-05-26 16:58:31 +02:00
Bram KragtenandGitHub 8291a84e3e Hide network config when not loaded (#9265) 2021-05-26 07:53:54 -07:00
b0e1f0f73a Add network configuration (#9210)
Co-authored-by: Bram Kragten <[email protected]>
2021-05-26 16:44:15 +02:00
Bram KragtenandGitHub a66b966e7d Fix a bunch of updates triggering updated (#9260) 2021-05-26 16:29:50 +02:00
Philip AllgaierandGitHub 5f56040c64 Add friendly_name to dev tools "Entity" column + fuzzy search (#7582) 2021-05-26 15:29:51 +02:00
Bram KragtenandGitHub eaccd22267 Fix chartjs deprecation warnings (#9261) 2021-05-26 15:05:01 +02:00
Bram KragtenandGitHub 27845a7345 Fix logbook height (#9258) 2021-05-26 12:44:10 +02:00
Bram KragtenandGitHub f7ef8180e4 Guard for undefined item in quick bar (#9259) 2021-05-26 12:43:59 +02:00
Bram KragtenandGitHub 5958eb9a55 Minor dependency bumps (#9249) 2021-05-26 12:04:39 +02:00
Bram Kragten 3ef2912b60 Fix typo in translation key 2021-05-26 11:19:09 +02:00
Joakim SørensenandGitHub fa9c6a765a Replace closing with closed in dialogs (#9257) 2021-05-26 11:10:27 +02:00
Bram KragtenandGitHub c4a8899780 Bump idb-keyval (#9248)
https://github.com/jakearchibald/idb-keyval#updating-from-3x
2021-05-26 10:22:38 +02:00
Bram KragtenandGitHub 3cc4628d03 Bump test dependencies (#9244) 2021-05-26 10:02:02 +02:00
GitHub Action b6c5223221 Translation update 2021-05-26 02:25:19 +00:00
Philip AllgaierandGitHub cbd6d4251c Prevent shrinking of percent value in supervisor metrics (#9033) 2021-05-26 00:24:30 +02:00
Bram KragtenandGitHub fdcbb5b432 Bump js-yaml (#9245) 2021-05-26 00:13:58 +02:00
Bram KragtenandGitHub de09e31815 Fix resetting theme, only fallback to light when theme doesnt support… (#9253) 2021-05-26 00:11:17 +02:00
Philip AllgaierandGitHub f55e911313 Prevent formatting for unknown attribute (#9252) 2021-05-26 00:08:41 +02:00
Bram KragtenandGitHub 465a91dbf3 Fix circulair progress producing scrollbars (#9247) 2021-05-25 23:59:24 +02:00
Bram KragtenandGitHub 835a7833ae Bump memoize one (#9243) 2021-05-25 23:53:58 +02:00
Bram KragtenandGitHub 179717d40c Fix rollup build (#9246) 2021-05-25 23:51:31 +02:00
Philip AllgaierandGitHub 3d4d789f7f Detect and format date & timestamp attributes (#9074) 2021-05-25 22:39:21 +02:00
Bram KragtenandGitHub d97fb19f05 Ingress: Wait for dialog to close before navigating (#9250) 2021-05-25 22:11:52 +02:00
0dd3757df2 Refresh snapshot create/restore dialogs (#9223)
Co-authored-by: Bram Kragten <[email protected]>
2021-05-25 21:29:32 +02:00
Philip AllgaierandGitHub c32a4546f3 Translate NC account connection state (#9167) 2021-05-25 18:12:56 +02:00
1bb025ccd0 Add log level changed message when user changes Z-Wave JS log level (#9238)
Co-authored-by: Bram Kragten <[email protected]>
2021-05-25 17:59:00 +02:00
2b8033a97f Prevent cutting off of attributes in more-info light (#9219)
* Prevent cutting off of attributes in more-info light

* Update src/dialogs/more-info/controls/more-info-light.ts

Co-authored-by: Bram Kragten <[email protected]>
2021-05-25 17:58:20 +02:00
Joakim SørensenandGitHub 21a3a8c594 Navigate cleanup (#9202) 2021-05-25 17:46:36 +02:00
1026e90296 Put attributes in more-info into a foldable section (#9220)
Co-authored-by: Bram Kragten <[email protected]>
2021-05-25 17:44:02 +02:00
Bram KragtenandGitHub 0eca602e61 Use comboBoxRenderer from lit-vaadin-helpers (#9201) 2021-05-25 13:27:49 +02:00
Philip AllgaierandGitHub 7f75ca81f1 Add support for custom themes to use dark mode (#8347) 2021-05-25 13:26:35 +02:00
Bram KragtenandGitHub 8af05e2726 Optimise data table and device dashboard (#9217) 2021-05-25 13:12:44 +02:00
Bram KragtenandGitHub 0a478ee1da Fix tabs styling (#9241) 2021-05-25 12:05:20 +02:00
GitHub Action a4bdc5a05f Translation update 2021-05-25 02:00:53 +00:00
d425767dae Ensure timer row uses correct state translation keys (#9143)
* Ensure timer row uses correct state translation keys

* Changes from review

* Update src/panels/lovelace/entity-rows/hui-timer-entity-row.ts

Co-authored-by: Bram Kragten <[email protected]>

* Move logic to data/timer

Co-authored-by: Bram Kragten <[email protected]>
2021-05-24 23:27:00 +02:00
Philip AllgaierandGitHub c78382c119 Make it clear that those are the installed add-ons (#9228) 2021-05-24 23:20:22 +02:00
Philip AllgaierandGitHub ee15ddfbc3 Show correct number of disabled integrations (#9232) 2021-05-24 22:47:28 +02:00
Philip AllgaierandGitHub 0af14eb77e Add refresh button to state dev tools (#9231) 2021-05-24 22:37:29 +02:00
GitHub Action 583cc4bc8a Translation update 2021-05-24 02:01:00 +00:00
GitHub Action 2ee92f48e6 Translation update 2021-05-23 02:04:38 +00:00
Franck NijhofandGitHub d05e02ab3e Add the Supervisor as an ignorable discovery source (#9229) 2021-05-22 19:26:27 +02:00
Joakim SørensenandGitHub abb9f8e233 Remove padding when narrow (#9209) 2021-05-22 06:12:51 -07:00
GitHub Action f873ef9b59 Translation update 2021-05-22 01:52:23 +00:00
Philip AllgaierandGitHub 1255b56522 Add missing translations to voice command dialog (#9221) 2021-05-21 10:11:31 +02:00
Bram KragtenandGitHub fd9bb4d8cc Make chrome work-around work in iframes (#9200) 2021-05-21 09:09:31 +02:00
GitHub Action 9328576b55 Translation update 2021-05-21 01:53:57 +00:00
70a1edd1dd Allow users to select time format for UI rendering (#9042)
Co-authored-by: Bram Kragten <[email protected]>
2021-05-20 16:23:53 +02:00
Bram KragtenandGitHub 9612bc78fe Merge pull request #9097 from home-assistant/dev 2021-05-04 23:21:05 +02:00
Bram KragtenandGitHub 2b86137388 Merge pull request #9079 from home-assistant/dev 2021-05-03 16:16:58 +02:00
Paulus SchoutsenandGitHub 8fdbe447c1 Merge pull request #9060 from home-assistant/dev
20210430.0
2021-04-30 12:43:33 -07:00
Bram KragtenandGitHub 764ae7e0b6 Merge pull request #9045 from home-assistant/dev 2021-04-29 22:21:03 +02:00
Paulus SchoutsenandGitHub 6b7e78320d Merge pull request #9024 from home-assistant/dev 2021-04-28 10:47:16 -07:00
738 changed files with 63187 additions and 33681 deletions
+52 -46
View File
@@ -28,64 +28,58 @@
"__BUILD__": false,
"__VERSION__": false,
"__STATIC_PATH__": false,
"Polymer": true,
"webkitSpeechRecognition": false,
"ResizeObserver": false
"Polymer": true
},
"env": {
"browser": true,
"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"],
@@ -103,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: [email protected]
- 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
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
require: "test-mocha/testconf.js",
timeout: 10000,
};
+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 -10
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,16 +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"),
];
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 -6
View File
@@ -52,16 +52,11 @@ const createRollupConfig = ({
browser: true,
rootDir: paths.polymer_dir,
}),
commonjs({
namedExports: {
"js-yaml": ["safeDump", "safeLoad"],
},
}),
commonjs(),
json(),
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";
+8 -1
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]) => {
@@ -70,7 +77,7 @@ class HaDemo extends HomeAssistantAppEl {
}
e.preventDefault();
navigate(this, href);
navigate(href);
},
{ capture: true }
);
+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 },
+2 -2
View File
@@ -1,7 +1,7 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { safeLoad } from "js-yaml";
import { load } from "js-yaml";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
class DemoCard extends PolymerElement {
@@ -80,7 +80,7 @@ class DemoCard extends PolymerElement {
card.removeChild(card.lastChild);
}
const el = this._createCardElement(safeLoad(config.config)[0]);
const el = this._createCardElement(load(config.config)[0]);
card.appendChild(el);
this._getSize(el);
}
@@ -1,4 +1,4 @@
import { safeDump } from "js-yaml";
import { dump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-card";
@@ -56,7 +56,7 @@ export class DemoAutomationDescribeAction extends LitElement {
(conf) => html`
<div class="action">
<span>${describeAction(this.hass, conf as any)}</span>
<pre>${safeDump(conf)}</pre>
<pre>${dump(conf)}</pre>
</div>
`
)}
@@ -1,4 +1,4 @@
import { safeDump } from "js-yaml";
import { dump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-card";
@@ -26,7 +26,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
(conf) => html`
<div class="condition">
<span>${describeCondition(conf as any)}</span>
<pre>${safeDump(conf)}</pre>
<pre>${dump(conf)}</pre>
</div>
`
)}
@@ -1,4 +1,4 @@
import { safeDump } from "js-yaml";
import { dump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-card";
@@ -29,7 +29,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
(conf) => html`
<div class="trigger">
<span>${describeTrigger(conf as any)}</span>
<pre>${safeDump(conf)}</pre>
<pre>${dump(conf)}</pre>
</div>
`
)}
+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({
@@ -120,7 +120,7 @@ class HassioAddonRepositoryEl extends LitElement {
}
private _addonTapped(ev) {
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}`);
navigate(`/hassio/addon/${ev.currentTarget.addon.slug}`);
}
static get styles(): CSSResultGroup {
+1 -1
View File
@@ -138,7 +138,7 @@ class HassioAddonStore extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
const repositoryUrl = extractSearchParam("repository_url");
navigate(this, "/hassio/store", true);
navigate("/hassio/store", { replace: true });
if (repositoryUrl) {
this._manageRepositories(repositoryUrl);
}
@@ -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,7 @@ import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import { DEFAULT_SCHEMA, Type } from "js-yaml";
import {
css,
CSSResultGroup,
@@ -11,12 +11,13 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
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";
@@ -27,6 +28,7 @@ import {
HassioAddonDetails,
HassioAddonSetOptionParams,
setHassioAddonOption,
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
@@ -38,6 +40,13 @@ import { hassioStyle } from "../../resources/hassio-style";
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
new Type("!secret", {
kind: "scalar",
construct: (data) => `!secret ${data}`,
}),
]);
@customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@@ -125,18 +134,21 @@ class HassioAddonConfig extends LitElement {
></ha-form>`
: html` <ha-yaml-editor
@value-changed=${this._configChanged}
.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
@@ -259,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,
@@ -269,8 +284,16 @@ class HassioAddonConfig extends LitElement {
this._error = undefined;
try {
const validation = await validateHassioAddonOption(
this.hass,
this.addon.slug,
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;
@@ -304,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>`
@@ -175,7 +175,7 @@ class HassioAddonDashboard extends LitElement {
if (!validAddon) {
this._error = this.supervisor.localize("my.error_addon_not_found");
} else {
navigate(this, `/hassio/addon/${requestedAddon}`, true);
navigate(`/hassio/addon/${requestedAddon}`, { replace: true });
}
}
}
+31 -18
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>
@@ -761,7 +764,7 @@ class HassioAddonInfo extends LitElement {
}
private _openIngress(): void {
navigate(this, `/hassio/ingress/${this.addon.slug}`);
navigate(`/hassio/ingress/${this.addon.slug}`);
}
private get _computeShowIngressUI(): boolean {
@@ -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,
@@ -1051,7 +1063,7 @@ class HassioAddonInfo extends LitElement {
}
private _openConfiguration(): void {
navigate(this, `/hassio/addon/${this.addon.slug}/config`);
navigate(`/hassio/addon/${this.addon.slug}/config`);
}
private async _uninstallClicked(ev: CustomEvent): Promise<void> {
@@ -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;
}
`,
];
}
+383
View File
@@ -0,0 +1,383 @@
import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import relativeTime from "../../../src/common/datetime/relative_time";
import { HASSDomEvent } from "../../../src/common/dom/fire_event";
import {
DataTableColumnContainer,
RowClickedEvent,
SelectionChangedEvent,
} from "../../../src/components/data-table/ha-data-table";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
fetchHassioBackups,
friendlyFolderName,
HassioBackup,
reloadHassioBackups,
removeBackup,
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
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 { 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-backups")
export class HassioBackups extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Object }) public route!: Route;
@property({ type: Boolean }) public narrow!: boolean;
@property({ type: Boolean }) public isWide!: boolean;
@state() private _selectedBackups: string[] = [];
@state() private _backups?: HassioBackup[] = [];
@query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
private _firstUpdatedCalled = false;
public connectedCallback(): void {
super.connectedCallback();
if (this.hass && this._firstUpdatedCalled) {
this.refreshData();
}
}
public async refreshData() {
await reloadHassioBackups(this.hass);
await this.fetchBackups();
}
private _computeBackupContent = (backup: HassioBackup): string => {
if (backup.type === "full") {
return this.supervisor.localize("backup.full_backup");
}
const content: string[] = [];
if (backup.content.homeassistant) {
content.push("Home Assistant");
}
if (backup.content.folders.length !== 0) {
for (const folder of backup.content.folders) {
content.push(friendlyFolderName[folder] || folder);
}
}
if (backup.content.addons.length !== 0) {
for (const addon of backup.content.addons) {
content.push(
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
)?.name || addon
);
}
}
return content.join(", ");
};
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this.hass && this.isConnected) {
this.refreshData();
}
this._firstUpdatedCalled = true;
}
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor?.localize("backup.name") || "",
sortable: true,
filterable: true,
grows: true,
template: (entry: string, backup: any) =>
html`${entry || backup.slug}
<div class="secondary">${backup.secondary}</div>`,
},
date: {
title: this.supervisor?.localize("backup.created") || "",
width: "15%",
direction: "desc",
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: string) =>
relativeTime(new Date(entry), this.hass.localize),
},
secondary: {
title: "",
hidden: true,
filterable: true,
},
})
);
private _backupData = memoizeOne((backups: HassioBackup[]) =>
backups.map((backup) => ({
...backup,
secondary: this._computeBackupContent(backup),
}))
);
protected render(): TemplateResult {
if (!this.supervisor) {
return html``;
}
return html`
<hass-tabs-subpage-data-table
.tabs=${supervisorTabs}
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("backup.no_backups")}
.narrow=${this.narrow}
.route=${this.route}
.columns=${this._columns(this.narrow)}
.data=${this._backupData(this._backups || [])}
id="slug"
@row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged}
clickable
selectable
hasFab
main-page
supervisor
>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>
${this.supervisor?.localize("common.reload")}
</mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item>
${this.supervisor?.localize("backup.upload_backup")}
</mwc-list-item>`
: ""}
</ha-button-menu>
${this._selectedBackups.length
? html`<div
class=${classMap({
"header-toolbar": this.narrow,
"table-header": !this.narrow,
})}
slot="header"
>
<p class="selected-txt">
${this.supervisor.localize("backup.selected", {
number: this._selectedBackups.length,
})}
</p>
<div class="header-btns">
${!this.narrow
? html`
<mwc-button
@click=${this._deleteSelected}
class="warning"
>
${this.supervisor.localize("backup.delete_selected")}
</mwc-button>
`
: html`
<mwc-icon-button
id="delete-btn"
class="warning"
@click=${this._deleteSelected}
>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("backup.delete_selected")}
</paper-tooltip>
`}
</div>
</div> `
: ""}
<ha-fab
slot="fab"
@click=${this._createBackup}
.label=${this.supervisor.localize("backup.create_backup")}
extended
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>
</hass-tabs-subpage-data-table>
`;
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this.refreshData();
break;
case 1:
this._showUploadBackupDialog();
break;
}
}
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedBackups = ev.detail.value;
}
private _showUploadBackupDialog() {
showBackupUploadDialog(this, {
showBackup: (slug: string) =>
showHassioBackupDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchBackups(),
}),
reloadBackup: () => this.refreshData(),
});
}
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("backup.delete_backup_title"),
text: this.supervisor.localize("backup.delete_backup_text", {
number: this._selectedBackups.length,
}),
confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
});
if (!confirm) {
return;
}
try {
await Promise.all(
this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
);
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("backup.failed_to_delete"),
text: extractApiErrorMessage(err),
});
return;
}
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
this._dataTable.clearSelection();
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id;
showHassioBackupDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchBackups(),
});
}
private _createBackup() {
if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, {
title: this.supervisor!.localize("backup.could_not_create"),
text: this.supervisor!.localize(
"backup.create_blocked_not_running",
"state",
this.supervisor!.info.state
),
});
return;
}
showHassioCreateBackupDialog(this, {
supervisor: this.supervisor!,
onCreate: () => this.fetchBackups(),
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
hassioStyle,
css`
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 58px;
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
}
.header-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--secondary-text-color);
position: relative;
top: -4px;
}
.selected-txt {
font-weight: bold;
padding-left: 16px;
color: var(--primary-text-color);
}
.table-header .selected-txt {
margin-top: 20px;
}
.header-toolbar .selected-txt {
font-size: 16px;
}
.header-toolbar .header-btns {
margin-right: -12px;
}
.header-btns > mwc-button,
.header-btns > mwc-icon-button {
margin: 8px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"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;
}
}
@@ -0,0 +1,448 @@
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
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 {
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";
import "./supervisor-formfield-label";
interface CheckboxItem {
slug: string;
checked: boolean;
name: string;
}
interface AddonCheckboxItem extends CheckboxItem {
version: string;
}
const _computeFolders = (folders): CheckboxItem[] => {
const list: CheckboxItem[] = [];
if (folders.includes("homeassistant")) {
list.push({
slug: "homeassistant",
name: "Home Assistant configuration",
checked: false,
});
}
if (folders.includes("ssl")) {
list.push({ slug: "ssl", name: "SSL", checked: false });
}
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 });
}
return list.sort((a, b) => (a.name > b.name ? 1 : -1));
};
const _computeAddons = (addons): AddonCheckboxItem[] =>
addons
.map((addon) => ({
slug: addon.slug,
name: addon.name,
version: addon.version,
checked: false,
}))
.sort((a, b) => (a.name > b.name ? 1 : -1));
@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 backup?: HassioBackupDetail;
@property() public backupType: HassioBackupDetail["type"] = "full";
@property({ attribute: false }) public folders?: CheckboxItem[];
@property({ attribute: false }) public addons?: AddonCheckboxItem[];
@property({ type: Boolean }) public homeAssistant = false;
@property({ type: Boolean }) public backupHasPassword = false;
@property({ type: Boolean }) public onboarding = false;
@property() public backupName = "";
@property() public backupPassword = "";
@property() public confirmBackupPassword = "";
public willUpdate(changedProps) {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
this.folders = _computeFolders(
this.backup
? this.backup.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
);
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.onboarding && !this.supervisor) {
return html``;
}
const foldersSection =
this.backupType === "partial" ? this._getSection("folders") : undefined;
const addonsSection =
this.backupType === "partial" ? this._getSection("addons") : undefined;
return html`
${this.backup
? html`<div class="details">
${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="backupName"
.label=${this._localize("name")}
.value=${this.backupName}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`}
${!this.backup || this.backup.type === "full"
? html`<div class="sub-header">
${!this.backup
? this._localize("type")
: this._localize("select_type")}
</div>
<div class="backup-types">
<ha-formfield .label=${this._localize("full_backup")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="full"
name="backupType"
.checked=${this.backupType === "full"}
>
</ha-radio>
</ha-formfield>
<ha-formfield .label=${this._localize("partial_backup")}>
<ha-radio
@change=${this._handleRadioValueChanged}
value="partial"
name="backupType"
.checked=${this.backupType === "partial"}
>
</ha-radio>
</ha-formfield>
</div>`
: ""}
${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.backup.homeassistant}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@click=${() => {
this.homeAssistant = !this.homeAssistant;
}}
>
</ha-checkbox>
</ha-formfield>
`
: ""}
${foldersSection?.templates.length
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this._localize("folders")}
.iconPath=${mdiFolder}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
@change=${this._toggleSection}
.checked=${foldersSection.checked}
.indeterminate=${foldersSection.indeterminate}
.section=${"folders"}
>
</ha-checkbox>
</ha-formfield>
<div class="section-content">${foldersSection.templates}</div>
`
: ""}
${addonsSection?.templates.length
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this._localize("addons")}
.iconPath=${mdiPuzzle}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
@change=${this._toggleSection}
.checked=${addonsSection.checked}
.indeterminate=${addonsSection.indeterminate}
.section=${"addons"}
>
</ha-checkbox>
</ha-formfield>
<div class="section-content">${addonsSection.templates}</div>
`
: ""}
</div> `
: ""}
${this.backupType === "partial" &&
(!this.backup || this.backupHasPassword)
? html`<hr />`
: ""}
${!this.backup
? html`<ha-formfield
class="password"
.label=${this._localize("password_protection")}
>
<ha-checkbox
.checked=${this.backupHasPassword}
@change=${this._toggleHasPassword}
>
</ha-checkbox>
</ha-formfield>`
: ""}
${this.backupHasPassword
? html`
<paper-input
.label=${this._localize("password")}
type="password"
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>`
: ""}
`
: ""}
`;
}
static get styles(): CSSResultGroup {
return css`
.partial-picker ha-formfield {
display: block;
}
.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;
}
hr {
border-color: var(--divider-color);
border-bottom: none;
margin: 16px 0;
}
.details {
color: var(--secondary-text-color);
}
.section-content {
display: flex;
flex-direction: column;
margin-left: 30px;
}
ha-formfield.password {
display: block;
margin: 0 -14px -16px;
}
.backup-types {
display: flex;
margin-left: -13px;
}
.sub-header {
margin-top: 8px;
}
`;
}
public backupDetails():
| HassioPartialBackupCreateParams
| HassioFullBackupCreateParams {
const data: any = {};
if (!this.backup) {
data.name = this.backupName || formatDate(new Date(), this.hass.locale);
}
if (this.backupHasPassword) {
data.password = this.backupPassword;
if (!this.backup) {
data.confirm_password = this.confirmBackupPassword;
}
}
if (this.backupType === "full") {
return data;
}
const addons = this.addons
?.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this.folders
?.filter((folder) => folder.checked)
.map((folder) => folder.slug);
if (addons?.length) {
data.addons = addons;
}
if (folders?.length) {
data.folders = folders;
}
if (this.homeAssistant) {
data.homeassistant = this.homeAssistant;
}
return data;
}
private _getSection(section: string) {
const templates: TemplateResult[] = [];
const addons =
section === "addons"
? new Map(
this.supervisor?.addon.addons.map((item) => [item.slug, item])
)
: undefined;
let checkedItems = 0;
this[section].forEach((item) => {
templates.push(html`<ha-formfield
.label=${html`<supervisor-formfield-label
.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`
: undefined}
.version=${item.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.item=${item}
.checked=${item.checked}
.section=${section}
@change=${this._updateSectionEntry}
>
</ha-checkbox>
</ha-formfield>`);
if (item.checked) {
checkedItems++;
}
});
const checked = checkedItems === this[section].length;
return {
templates,
checked,
indeterminate: !checked && checkedItems !== 0,
};
}
private _handleRadioValueChanged(ev: CustomEvent) {
const input = ev.currentTarget as HaRadio;
this[input.name] = input.value;
}
private _handleTextValueChanged(ev: PolymerChangedEvent<string>) {
const input = ev.currentTarget as PaperInputElement;
this[input.name!] = ev.detail.value;
}
private _toggleHasPassword(): void {
this.backupHasPassword = !this.backupHasPassword;
}
private _toggleSection(ev): void {
const section = ev.currentTarget.section;
this[section] = (section === "addons" ? this.addons : this.folders)!.map(
(item) => ({
...item,
checked: ev.currentTarget.checked,
})
);
}
private _updateSectionEntry(ev): void {
const item = ev.currentTarget.item;
const section = ev.currentTarget.section;
this[section] = this[section].map((entry) =>
entry.slug === item.slug
? {
...entry,
checked: ev.currentTarget.checked,
}
: entry
);
}
}
declare global {
interface HTMLElementTagNameMap {
"supervisor-backup-content": SupervisorBackupContent;
}
}
@@ -0,0 +1,54 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-svg-icon";
@customElement("supervisor-formfield-label")
class SupervisorFormfieldLabel extends LitElement {
@property({ type: String }) public label!: string;
@property({ type: String }) public imageUrl?: string;
@property({ type: String }) public iconPath?: string;
@property({ type: String }) public version?: string;
protected render(): TemplateResult {
return html`
${this.imageUrl
? html`<img loading="lazy" .src=${this.imageUrl} class="icon" />`
: this.iconPath
? html`<ha-svg-icon .path=${this.iconPath} class="icon"></ha-svg-icon>`
: ""}
<span class="label">${this.label}</span>
${this.version
? html`<span class="version">(${this.version})</span>`
: ""}
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
display: flex;
align-items: center;
}
.label {
margin-right: 4px;
}
.version {
color: var(--secondary-text-color);
}
.icon {
max-height: 22px;
max-width: 22px;
margin-right: 8px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"supervisor-formfield-label": SupervisorFormfieldLabel;
}
}
@@ -64,6 +64,7 @@ class SupervisorMetric extends LitElement {
.value {
width: 48px;
padding-right: 4px;
flex-shrink: 0;
}
`;
}
+4 -4
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}>
@@ -96,11 +96,11 @@ class HassioAddons extends LitElement {
}
private _addonTapped(ev: any): void {
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}/info`);
navigate(`/hassio/addon/${ev.currentTarget.addon.slug}/info`);
}
private _openStore(): void {
navigate(this, "/hassio/store");
navigate("/hassio/store");
}
}
+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;
}
}
@@ -0,0 +1,154 @@
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 {
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-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
@customElement("dialog-hassio-create-backup")
class HassioCreateBackupDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateBackupDialogParams;
@state() private _error?: string;
@state() private _creatingBackup = false;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
public showDialog(params: HassioCreateBackupDialogParams) {
this._dialogParams = params;
this._creatingBackup = false;
}
public closeDialog() {
this._dialogParams = undefined;
this._creatingBackup = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this._dialogParams.supervisor.localize("backup.create_backup")
)}
>
${this._creatingBackup
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
>
</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._creatingBackup}
slot="primaryAction"
@click=${this._createBackup}
>
${this._dialogParams.supervisor.localize("backup.create")}
</mwc-button>
</ha-dialog>
`;
}
private async _createBackup(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize(
"backup.could_not_create"
),
text: this._dialogParams!.supervisor.localize(
"backup.create_blocked_not_running",
"state",
this._dialogParams!.supervisor.info.state
),
});
return;
}
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 (
backupDetails.password &&
backupDetails.password !== backupDetails.confirm_password
) {
this._error = this._dialogParams!.supervisor.localize(
"backup.passwords_not_matching"
);
this._creatingBackup = false;
return;
}
delete backupDetails.confirm_password;
try {
if (this._backupContent.backupType === "full") {
await createHassioFullBackup(this.hass, backupDetails);
} else {
await createHassioPartialBackup(this.hass, backupDetails);
}
this._dialogParams!.onCreate();
this.closeDialog();
} catch (err) {
this._error = extractApiErrorMessage(err);
}
this._creatingBackup = false;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-circular-progress {
display: block;
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"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";
@@ -45,7 +45,7 @@ class HassioRegistriesDialog extends LitElement {
return html`
<ha-dialog
.open=${this._opened}
@closing=${this.closeDialog}
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
@@ -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()
);
}
@@ -244,9 +244,6 @@ class HassioRegistriesDialog extends LitElement {
mwc-list-item span[slot="secondary"] {
color: var(--secondary-text-color);
}
ha-paper-dropdown-menu {
display: block;
}
`,
];
}
@@ -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";
@@ -67,7 +68,7 @@ class HassioRepositoriesDialog extends LitElement {
return html`
<ha-dialog
.open=${this._opened}
@closing=${this.closeDialog}
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
@@ -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(
@@ -150,9 +153,6 @@ class HassioRepositoriesDialog extends LitElement {
mwc-button {
margin-left: 8px;
}
ha-paper-dropdown-menu {
display: block;
}
ha-circular-progress {
display: block;
margin: 32px;
@@ -164,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,360 +0,0 @@
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { formatDate } from "../../../../src/common/datetime/format_date";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { compare } from "../../../../src/common/string/compare";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-checkbox";
import type { HaCheckbox } from "../../../../src/components/ha-checkbox";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-radio";
import type { HaRadio } from "../../../../src/components/ha-radio";
import "../../../../src/components/ha-settings-row";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
HassioFullSnapshotCreateParams,
HassioPartialSnapshotCreateParams,
HassioSnapshot,
} from "../../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { PolymerChangedEvent } from "../../../../src/polymer-types";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot";
interface CheckboxItem {
slug: string;
checked: boolean;
name?: string;
version?: string;
}
const folderList = () => [
{
slug: "homeassistant",
checked: true,
},
{ slug: "ssl", checked: true },
{ slug: "share", checked: true },
{ slug: "media", checked: true },
{ slug: "addons/local", checked: true },
];
@customElement("dialog-hassio-create-snapshot")
class HassioCreateSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _snapshotName = "";
@state() private _snapshotPassword = "";
@state() private _snapshotHasPassword = false;
@state() private _snapshotType: HassioSnapshot["type"] = "full";
@state() private _dialogParams?: HassioCreateSnapshotDialogParams;
@state() private _addonList: CheckboxItem[] = [];
@state() private _folderList: CheckboxItem[] = folderList();
@state() private _error = "";
public showDialog(params: HassioCreateSnapshotDialogParams) {
this._dialogParams = params;
this._addonList = this._dialogParams.supervisor.supervisor.addons
.map((addon) => ({
slug: addon.slug,
name: addon.name,
version: addon.version,
checked: true,
}))
.sort((a, b) => compare(a.name, b.name));
this._snapshotType = "full";
this._error = "";
this._folderList = folderList();
this._snapshotHasPassword = false;
this._snapshotPassword = "";
this._snapshotName = "";
}
public closeDialog() {
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
return html`
<ha-dialog
open
@closing=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this._dialogParams.supervisor.localize("snapshot.create_snapshot")
)}
>
<paper-input
name="snapshotName"
.label=${this._dialogParams.supervisor.localize("snapshot.name")}
.value=${this._snapshotName}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>
<div class="snapshot-types">
<div>
${this._dialogParams.supervisor.localize("snapshot.type")}:
</div>
<ha-formfield
.label=${this._dialogParams.supervisor.localize(
"snapshot.full_snapshot"
)}
>
<ha-radio
@change=${this._handleRadioValueChanged}
value="full"
name="snapshotType"
.checked=${this._snapshotType === "full"}
>
</ha-radio>
</ha-formfield>
<ha-formfield
.label=${this._dialogParams.supervisor.localize(
"snapshot.partial_snapshot"
)}
>
<ha-radio
@change=${this._handleRadioValueChanged}
value="partial"
name="snapshotType"
.checked=${this._snapshotType === "partial"}
>
</ha-radio>
</ha-formfield>
</div>
${
this._snapshotType === "full"
? undefined
: html`
${this._dialogParams.supervisor.localize("snapshot.folders")}:
<div class="checkbox-section">
${this._folderList.map(
(folder, idx) => html`
<div class="checkbox-line">
<ha-checkbox
.idx=${idx}
.checked=${folder.checked}
@change=${this._folderChecked}
slot="prefix"
>
</ha-checkbox>
<span>
${this._dialogParams!.supervisor.localize(
`snapshot.folder.${folder.slug}`
)}
</span>
</div>
`
)}
</div>
${this._dialogParams.supervisor.localize("snapshot.addons")}:
<div class="checkbox-section">
${this._addonList.map(
(addon, idx) => html`
<div class="checkbox-line">
<ha-checkbox
.idx=${idx}
.checked=${addon.checked}
@change=${this._addonChecked}
slot="prefix"
>
</ha-checkbox>
<span>
${addon.name}<span class="version">
(${addon.version})
</span>
</span>
</div>
`
)}
</div>
`
}
${this._dialogParams.supervisor.localize("snapshot.security")}:
<div class="checkbox-section">
<div class="checkbox-line">
<ha-checkbox
.checked=${this._snapshotHasPassword}
@change=${this._handleCheckboxValueChanged}
slot="prefix"
>
</ha-checkbox>
<span>
${this._dialogParams.supervisor.localize(
"snapshot.password_protection"
)}
</span>
</span>
</div>
</div>
${
this._snapshotHasPassword
? html`
<paper-input
.label=${this._dialogParams.supervisor.localize(
"snapshot.password"
)}
type="password"
name="snapshotPassword"
.value=${this._snapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>
`
: undefined
}
${
this._error !== ""
? html` <p class="error">${this._error}</p> `
: undefined
}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")}
</mwc-button>
<ha-progress-button slot="primaryAction" @click=${this._createSnapshot}>
${this._dialogParams.supervisor.localize("snapshot.create")}
</ha-progress-button>
</ha-dialog>
`;
}
private _handleTextValueChanged(ev: PolymerChangedEvent<string>) {
const input = ev.currentTarget as PaperInputElement;
this[`_${input.name}`] = ev.detail.value;
}
private _handleCheckboxValueChanged(ev: CustomEvent) {
const input = ev.currentTarget as HaCheckbox;
this._snapshotHasPassword = input.checked;
}
private _handleRadioValueChanged(ev: CustomEvent) {
const input = ev.currentTarget as HaRadio;
this[`_${input.name}`] = input.value;
}
private _folderChecked(ev) {
const { idx, checked } = ev.currentTarget!;
this._folderList = this._folderList.map((folder, curIdx) =>
curIdx === idx ? { ...folder, checked } : folder
);
}
private _addonChecked(ev) {
const { idx, checked } = ev.currentTarget!;
this._addonList = this._addonList.map((addon, curIdx) =>
curIdx === idx ? { ...addon, checked } : addon
);
}
private async _createSnapshot(ev: CustomEvent): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize(
"snapshot.could_not_create"
),
text: this._dialogParams!.supervisor.localize(
"snapshot.create_blocked_not_running",
"state",
this._dialogParams!.supervisor.info.state
),
});
return;
}
const button = ev.currentTarget as any;
button.progress = true;
this._error = "";
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
this._error = this._dialogParams!.supervisor.localize(
"snapshot.enter_password"
);
button.progress = false;
return;
}
const name = this._snapshotName || formatDate(new Date(), this.hass.locale);
try {
if (this._snapshotType === "full") {
const data: HassioFullSnapshotCreateParams = { name };
if (this._snapshotHasPassword) {
data.password = this._snapshotPassword;
}
await createHassioFullSnapshot(this.hass, data);
} else {
const data: HassioPartialSnapshotCreateParams = {
name,
folders: this._folderList
.filter((folder) => folder.checked)
.map((folder) => folder.slug),
addons: this._addonList
.filter((addon) => addon.checked)
.map((addon) => addon.slug),
};
if (this._snapshotHasPassword) {
data.password = this._snapshotPassword;
}
await createHassioPartialSnapshot(this.hass, data);
}
this._dialogParams!.onCreate();
this.closeDialog();
} catch (err) {
this._error = extractApiErrorMessage(err);
}
button.progress = false;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
.error {
color: var(--error-color);
}
paper-input[type="password"] {
display: block;
margin: 4px 0 4px 16px;
}
span.version {
color: var(--secondary-text-color);
}
.checkbox-section {
display: grid;
}
.checkbox-line {
display: inline-flex;
align-items: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog;
}
}
@@ -1,513 +0,0 @@
import "@material/mwc-button";
import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
import "@polymer/paper-checkbox/paper-checkbox";
import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { formatDateTime } from "../../../../src/common/datetime/format_date_time";
import { fireEvent } from "../../../../src/common/dom/fire_event";
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 {
fetchHassioSnapshotInfo,
HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { PolymerChangedEvent } from "../../../../src/polymer-types";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
const _computeFolders = (folders) => {
const list: Array<{ slug: string; name: string; checked: boolean }> = [];
if (folders.includes("homeassistant")) {
list.push({
slug: "homeassistant",
name: "Home Assistant configuration",
checked: true,
});
}
if (folders.includes("ssl")) {
list.push({ slug: "ssl", name: "SSL", checked: true });
}
if (folders.includes("share")) {
list.push({ slug: "share", name: "Share", checked: true });
}
if (folders.includes("addons/local")) {
list.push({ slug: "addons/local", name: "Local add-ons", checked: true });
}
return list;
};
const _computeAddons = (addons) =>
addons.map((addon) => ({
slug: addon.slug,
name: addon.name,
version: addon.version,
checked: true,
}));
interface AddonItem {
slug: string;
name: string;
version: string;
checked: boolean | null | undefined;
}
interface FolderItem {
slug: string;
name: string;
checked: boolean | null | undefined;
}
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor?: Supervisor;
@state() private _error?: string;
@state() private _onboarding = false;
@state() private _snapshot?: HassioSnapshotDetail;
@state() private _folders!: FolderItem[];
@state() private _addons!: AddonItem[];
@state() private _dialogParams?: HassioSnapshotDialogParams;
@state() private _snapshotPassword!: string;
@state() private _restoreHass = true;
public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._folders = _computeFolders(
this._snapshot?.folders
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
this._addons = _computeAddons(
this._snapshot?.addons
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
this._dialogParams = params;
this._onboarding = params.onboarding ?? false;
this.supervisor = params.supervisor;
if (!this._snapshot.homeassistant) {
this._restoreHass = false;
}
}
protected render(): TemplateResult {
if (!this._dialogParams || !this._snapshot) {
return html``;
}
return html`
<ha-dialog open @closing=${this._closeDialog} .heading=${true}>
<div slot="heading">
<ha-header-bar>
<span slot="title"> ${this._computeName} </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
<div class="details">
${this._snapshot.type === "full"
? "Full snapshot"
: "Partial snapshot"}
(${this._computeSize})<br />
${formatDateTime(new Date(this._snapshot.date), this.hass.locale)}
</div>
${this._snapshot.homeassistant
? html`<div>Home Assistant:</div>
<paper-checkbox
.checked=${this._restoreHass}
@change="${(ev: Event) => {
this._restoreHass = (ev.target as PaperCheckboxElement).checked!;
}}"
>
Home Assistant
<span class="version">(${this._snapshot.homeassistant})</span>
</paper-checkbox>`
: ""}
${this._folders.length
? html`
<div>Folders:</div>
<paper-dialog-scrollable class="no-margin-top">
${this._folders.map(
(item) => html`
<paper-checkbox
.checked=${item.checked}
@change="${(ev: Event) =>
this._updateFolders(
item,
(ev.target as PaperCheckboxElement).checked
)}"
>
${item.name}
</paper-checkbox>
`
)}
</paper-dialog-scrollable>
`
: ""}
${this._addons.length
? html`
<div>Add-on:</div>
<paper-dialog-scrollable class="no-margin-top">
${this._addons.map(
(item) => html`
<paper-checkbox
.checked=${item.checked}
@change="${(ev: Event) =>
this._updateAddons(
item,
(ev.target as PaperCheckboxElement).checked
)}"
>
${item.name}
<span class="version">(${item.version})</span>
</paper-checkbox>
`
)}
</paper-dialog-scrollable>
`
: ""}
${this._snapshot.protected
? html`
<paper-input
autofocus=""
label="Password"
type="password"
@value-changed=${this._passwordInput}
.value=${this._snapshotPassword}
></paper-input>
`
: ""}
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
<div class="button-row" slot="primaryAction">
<mwc-button @click=${this._partialRestoreClicked}>
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon>
Restore Selected
</mwc-button>
${!this._onboarding
? html`
<mwc-button @click=${this._deleteClicked}>
<ha-svg-icon .path=${mdiDelete} class="icon warning">
</ha-svg-icon>
<span class="warning">Delete Snapshot</span>
</mwc-button>
`
: ""}
</div>
<div class="button-row" slot="secondaryAction">
${this._snapshot.type === "full"
? html`
<mwc-button @click=${this._fullRestoreClicked}>
<ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon>
Restore Everything
</mwc-button>
`
: ""}
${!this._onboarding
? html`<mwc-button @click=${this._downloadClicked}>
<ha-svg-icon .path=${mdiDownload} class="icon"></ha-svg-icon>
Download Snapshot
</mwc-button>`
: ""}
</div>
</ha-dialog>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
paper-checkbox {
display: block;
margin: 4px;
}
mwc-button ha-svg-icon {
margin-right: 4px;
}
.button-row {
display: grid;
gap: 8px;
margin-right: 8px;
}
.details {
color: var(--secondary-text-color);
}
.warning,
.error {
color: var(--error-color);
}
.buttons li {
list-style-type: none;
}
.buttons .icon {
margin-right: 16px;
}
.no-margin-top {
margin-top: 0;
}
span.version {
color: var(--secondary-text-color);
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
}
/* overrule the ha-style-dialog max-height on small screens */
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-header-bar {
--mdc-theme-primary: var(--app-header-background-color);
--mdc-theme-on-primary: var(--app-header-text-color, white);
}
}
`,
];
}
private _updateFolders(item: FolderItem, value: boolean | null | undefined) {
this._folders = this._folders.map((folder) => {
if (folder.slug === item.slug) {
folder.checked = value;
}
return folder;
});
}
private _updateAddons(item: AddonItem, value: boolean | null | undefined) {
this._addons = this._addons.map((addon) => {
if (addon.slug === item.slug) {
addon.checked = value;
}
return addon;
});
}
private _passwordInput(ev: PolymerChangedEvent<string>) {
this._snapshotPassword = ev.detail.value;
}
private async _partialRestoreClicked() {
if (
this.supervisor !== undefined &&
this.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
const addons = this._addons
.filter((addon) => addon.checked)
.map((addon) => addon.slug);
const folders = this._folders
.filter((folder) => folder.checked)
.map((folder) => folder.slug);
const data: {
homeassistant: boolean;
addons: any;
folders: any;
password?: string;
} = {
homeassistant: this._restoreHass,
addons,
folders,
};
if (this._snapshot!.protected) {
data.password = this._snapshotPassword;
}
if (!this._onboarding) {
this.hass
.callApi(
"POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
data
)
.then(
() => {
alert("Snapshot restored!");
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(data),
});
this._closeDialog();
}
}
private async _fullRestoreClicked() {
if (
this.supervisor !== undefined &&
this.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title:
"Are you sure you want to wipe your system and restore this snapshot?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
const data = this._snapshot!.protected
? { password: this._snapshotPassword }
: undefined;
if (!this._onboarding) {
this.hass
.callApi(
"POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`,
data
)
.then(
() => {
alert("Snapshot restored!");
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(data),
});
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) {
alert(`Error: ${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 name = this._computeName.replace(/[^a-z0-9]+/gi, "_");
const a = document.createElement("a");
a.href = signedPath.path;
a.download = `Hass_io_${name}.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";
}
private get _computeSize() {
return Math.ceil(this._snapshot!.size * 10) / 10 + " MB";
}
private _closeDialog() {
this._dialogParams = undefined;
this._snapshot = undefined;
this._snapshotPassword = "";
this._folders = [];
this._addons = [];
}
}
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>;
}
+6
View File
@@ -15,5 +15,11 @@ body {
padding: 0;
height: 100vh;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #111111;
color: #e1e1e1;
}
}
`;
document.head.appendChild(styleEl);
+9 -8
View File
@@ -4,6 +4,7 @@ import { atLeastVersion } from "../../src/common/config/version";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../src/common/dom/fire_event";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { mainWindow } from "../../src/common/dom/get_main_window";
import { navigate } from "../../src/common/navigate";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor";
@@ -50,7 +51,7 @@ export class HassioMain extends SupervisorBaseElement {
// Joakim - April 26, 2021
// Due to changes in behavior in Google Chrome, we changed navigate to listen on the top element
top.addEventListener("location-changed", (ev) =>
mainWindow.addEventListener("location-changed", (ev) =>
// @ts-ignore
fireEvent(this, ev.type, ev.detail, {
bubbles: false,
@@ -62,7 +63,7 @@ export class HassioMain extends SupervisorBaseElement {
document.body.addEventListener("click", (ev) => {
const href = isNavigationClick(ev);
if (href) {
navigate(document.body, href);
navigate(href);
}
});
@@ -102,7 +103,7 @@ export class HassioMain extends SupervisorBaseElement {
private _applyTheme() {
let themeName: string;
let options: Partial<HomeAssistant["selectedTheme"]> | undefined;
let themeSettings: Partial<HomeAssistant["selectedTheme"]> | undefined;
if (atLeastVersion(this.hass.config.version, 0, 114)) {
themeName =
@@ -111,16 +112,16 @@ export class HassioMain extends SupervisorBaseElement {
? this.hass.themes.default_dark_theme!
: this.hass.themes.default_theme);
options = this.hass.selectedTheme;
if (themeName === "default" && options?.dark === undefined) {
options = {
themeSettings = this.hass.selectedTheme;
if (themeSettings?.dark === undefined) {
themeSettings = {
...this.hass.selectedTheme,
dark: this.hass.themes.darkMode,
};
}
} else {
themeName =
((this.hass.selectedTheme as unknown) as string) ||
(this.hass.selectedTheme as unknown as string) ||
this.hass.themes.default_theme;
}
@@ -128,7 +129,7 @@ export class HassioMain extends SupervisorBaseElement {
this.parentElement,
this.hass.themes,
themeName,
options
themeSettings
);
}
}
+9 -6
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",
@@ -89,7 +92,7 @@ class HassioMyRedirect extends LitElement {
return;
}
navigate(this, url, true);
navigate(url, { replace: true });
}
protected render(): TemplateResult {
+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,
},
{
+19 -5
View File
@@ -11,6 +11,7 @@ import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import { nextRender } from "../../../src/common/util/render-status";
import {
fetchHassioAddonInfo,
HassioAddonDetails,
@@ -95,17 +96,26 @@ class HassioIngressView extends LitElement {
text: extractApiErrorMessage(err),
title: requestedAddon,
});
history.back();
await nextRender();
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,
});
history.back();
await nextRender();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
} else {
navigate(this, `/hassio/ingress/${addonInfo.slug}`, true);
navigate(`/hassio/ingress/${addonInfo.slug}`, { replace: true });
}
}
}
@@ -140,6 +150,7 @@ class HassioIngressView extends LitElement {
text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor",
});
await nextRender();
history.back();
return;
}
@@ -149,6 +160,7 @@ class HassioIngressView extends LitElement {
text: "Add-on does not support Ingress",
title: addon.name,
});
await nextRender();
history.back();
return;
}
@@ -158,7 +170,8 @@ class HassioIngressView extends LitElement {
text: "Add-on is not running. Please start it first",
title: addon.name,
});
navigate(this, `/hassio/addon/${addon.slug}/info`, true);
await nextRender();
navigate(`/hassio/addon/${addon.slug}/info`, { replace: true });
return;
}
@@ -171,6 +184,7 @@ class HassioIngressView extends LitElement {
text: "Unable to create an Ingress session",
title: addon.name,
});
await nextRender();
history.back();
return;
}
-255
View File
@@ -1,255 +0,0 @@
import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical, mdiPlus } from "@mdi/js";
import {
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import relativeTime from "../../../src/common/datetime/relative_time";
import { HASSDomEvent } from "../../../src/common/dom/fire_event";
import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../src/components/data-table/ha-data-table";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab";
import {
fetchHassioSnapshots,
friendlyFolderName,
HassioSnapshot,
reloadHassioSnapshots,
} from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-tabs-subpage-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 { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-snapshots")
export class HassioSnapshots extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Object }) public route!: Route;
@property({ type: Boolean }) public narrow!: boolean;
@property({ type: Boolean }) public isWide!: boolean;
private _firstUpdatedCalled = false;
@state() private _snapshots?: HassioSnapshot[] = [];
public connectedCallback(): void {
super.connectedCallback();
if (this.hass && this._firstUpdatedCalled) {
this.refreshData();
}
}
public async refreshData() {
await reloadHassioSnapshots(this.hass);
await this.fetchSnapshots();
}
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => {
if (snapshot.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot");
}
const content: string[] = [];
if (snapshot.content.homeassistant) {
content.push("Home Assistant");
}
if (snapshot.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) {
content.push(friendlyFolderName[folder] || folder);
}
}
if (snapshot.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) {
content.push(
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
)?.name || addon
);
}
}
return content.join(", ");
};
protected firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this.hass && this.isConnected) {
this.refreshData();
}
this._firstUpdatedCalled = true;
}
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor?.localize("snapshot.name") || "",
sortable: true,
filterable: true,
grows: true,
template: (entry: string, snapshot: any) =>
html`${entry || snapshot.slug}
<div class="secondary">${snapshot.secondary}</div>`,
},
date: {
title: this.supervisor?.localize("snapshot.created") || "",
width: "15%",
direction: "desc",
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: string) =>
relativeTime(new Date(entry), this.hass.localize),
},
secondary: {
title: "",
hidden: true,
filterable: true,
},
})
);
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) =>
snapshots.map((snapshot) => ({
...snapshot,
secondary: this._computeSnapshotContent(snapshot),
}))
);
protected render(): TemplateResult {
if (!this.supervisor) {
return html``;
}
return html`
<hass-tabs-subpage-data-table
.tabs=${supervisorTabs}
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")}
.narrow=${this.narrow}
.route=${this.route}
.columns=${this._columns(this.narrow)}
.data=${this._snapshotData(this._snapshots || [])}
id="slug"
@row-click=${this._handleRowClicked}
clickable
hasFab
main-page
supervisor
>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>
${this.supervisor?.localize("common.reload")}
</mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item>
${this.supervisor?.localize("snapshot.upload_snapshot")}
</mwc-list-item>`
: ""}
</ha-button-menu>
<ha-fab
slot="fab"
@click=${this._createSnapshot}
.label=${this.supervisor.localize("snapshot.create_snapshot")}
extended
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>
</hass-tabs-subpage-data-table>
`;
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this.refreshData();
break;
case 1:
this._showUploadSnapshotDialog();
break;
}
}
private _showUploadSnapshotDialog() {
showSnapshotUploadDialog(this, {
showSnapshot: (slug: string) =>
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(),
}),
reloadSnapshot: () => this.refreshData(),
});
}
private async fetchSnapshots() {
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id;
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchSnapshots(),
});
}
private _createSnapshot() {
if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, {
title: this.supervisor!.localize("snapshot.could_not_create"),
text: this.supervisor!.localize(
"snapshot.create_blocked_not_running",
"state",
this.supervisor!.info.state
),
});
return;
}
showHassioCreateSnapshotDialog(this, {
supervisor: this.supervisor!,
onCreate: () => this.fetchSnapshots(),
});
}
static get styles(): CSSResultGroup {
return [haStyle, hassioStyle];
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-snapshots": HassioSnapshots;
}
}
+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 { safeDump } 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>${safeDump(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"
+119 -125
View File
@@ -16,125 +16,122 @@
"lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md",
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
"format": "yarn run format:eslint && yarn run format:prettier",
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
"test": "yarn run lint && yarn run mocha"
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
"test": "yarn run mocha"
},
"author": "Paulus Schoutsen <[email protected]> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@braintree/sanitize-url": "^5.0.0",
"@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.24",
"@formatjs/intl-pluralrules": "^4.0.18",
"@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/[email protected]#./.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-card": "^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",
"comlink": "^4.3.0",
"core-js": "^3.6.5",
"cropperjs": "^1.5.7",
"@webcomponents/webcomponentsjs": "^2.2.10",
"chart.js": "^3.3.2",
"comlink": "^4.3.1",
"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.3",
"home-assistant-js-websocket": "^5.10.0",
"idb-keyval": "^3.2.0",
"intl-messageformat": "^9.6.13",
"js-yaml": "^3.13.1",
"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",
"marked": "2.0.0",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",
"lit": "^2.0.0-rc.3",
"lit-vaadin-helpers": "^0.1.3",
"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.1",
"tsparticles": "^1.19.2",
"tinykeys": "^1.1.3",
"tsparticles": "^1.34.0",
"unfetch": "^4.1.0",
"vis-data": "^7.1.1",
"vis-data": "^7.1.2",
"vis-network": "^8.5.4",
"vue": "^2.6.11",
"vue": "^2.6.12",
"vue2-daterange-picker": "^0.5.1",
"web-animations-js": "^2.3.2",
"workbox-cacheable-response": "^6.1.5",
@@ -146,17 +143,17 @@
"xss": "^1.0.9"
},
"devDependencies": {
"@babel/core": "^7.14.0",
"@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.0",
"@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",
@@ -164,38 +161,35 @@
"@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-replace": "^2.3.2",
"@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^5.0.11",
"@types/chromecast-caf-receiver": "5.0.12",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/js-yaml": "^3.12.1",
"@types/leaflet": "^1.7.0",
"@types/leaflet-draw": "^1.0.3",
"@types/marked": "^1.2.2",
"@types/memoize-one": "4.1.0",
"@types/mocha": "^7.0.2",
"@types/resize-observer-browser": "^0.1.3",
"@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",
"chai": "^4.2.0",
"cpx": "^1.5.0",
"babel-loader": "^8.2.2",
"chai": "^4.3.4",
"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",
"gulp": "^4.0.0",
"gulp": "^4.0.2",
"gulp-foreach": "^0.1.0",
"gulp-json-transform": "^0.4.6",
"gulp-merge-json": "^1.3.1",
@@ -203,45 +197,45 @@
"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",
"map-stream": "^0.0.7",
"merge-stream": "^1.0.1",
"mocha": "^7.2.0",
"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",
"rollup-plugin-terser": "^5.3.0",
"rollup-plugin-visualizer": "^4.0.4",
"serve": "^11.3.0",
"sinon": "^7.3.1",
"serve": "^11.3.2",
"sinon": "^11.0.0",
"source-map-url": "^0.4.0",
"systemjs": "^6.3.2",
"terser-webpack-plugin": "^5.1.1",
"terser-webpack-plugin": "^5.1.4",
"ts-lit-plugin": "^1.2.1",
"ts-mocha": "^7.0.0",
"typescript": "^4.2.4",
"ts-mocha": "^8.0.0",
"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/[email protected]#./.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

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