Compare commits

..

210 Commits

Author SHA1 Message Date
Ludeeus 21574edb44 remove debug 2021-02-21 13:15:05 +00:00
Ludeeus 42023ed4a5 init 2021-02-21 13:09:43 +00:00
GitHub Action 8bfc8ece9d Translation update 2021-02-21 01:21:30 +00:00
GitHub Action 2d3cf7d84d Translation update 2021-02-20 01:18:25 +00:00
Franck Nijhof 520ef8f1df Update GitHub Issue Form template (#8423) 2021-02-19 22:13:27 +01:00
Bram Kragten f251d4267f Revert "Allow viewport scaling (zooming) of frontend" (#8353)
This reverts commit da9faccada.
2021-02-19 18:06:36 +01:00
Bram Kragten 2052a5351c Ha-form: Don't change data (#8277) 2021-02-19 18:03:31 +01:00
Bram Kragten 9807d0aede Move localizing to render (#8419) 2021-02-19 18:02:25 +01:00
Bram Kragten a41afcd714 Update lovelace call service action (#8421) 2021-02-19 17:58:14 +01:00
Bram Kragten d93d2b5945 Fix password field in ha-form (#8400) 2021-02-19 17:47:51 +01:00
Bram Kragten d54a129605 Bump marked (#8420) 2021-02-19 17:46:33 +01:00
Philip Allgaier 77911980cb Correctly handle seconds in top "delay" key (#8415)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-02-19 11:03:25 +01:00
Bram Kragten d51fd1e2f9 Add supervisor_logs and supervisor_info redirects (#8417) 2021-02-19 10:01:21 +01:00
GitHub Action fe54f8eb16 Translation update 2021-02-19 01:18:52 +00:00
Bram Kragten fc7c4af27a Add more redirects (#8413) 2021-02-18 20:35:16 +01:00
Joakim Sørensen 09e7600d86 Use websockets (#8403)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-02-18 18:18:05 +01:00
GitHub Action 17410874e3 Translation update 2021-02-18 01:18:34 +00:00
GitHub Action 03d4174163 Translation update 2021-02-17 01:18:01 +00:00
Bram Kragten 99eff73b0d Add support for target to automation call service action (#8372) 2021-02-16 21:46:47 +01:00
Joakim Sørensen acefa39796 Update supervisor info on addon action (#8404) 2021-02-16 21:38:23 +01:00
Joakim Sørensen c01c0528a6 Show options if no options and schema (#8408) 2021-02-16 21:25:59 +01:00
Bram Kragten 0ec58007c9 Add my support to supervisor (#8405)
* Add my support to supervisor

* Remove localize

* Comments

* Update ha-panel-my.ts
2021-02-16 19:50:35 +01:00
GitHub Action e8daf88729 Translation update 2021-02-16 01:18:09 +00:00
Matteo Agnoletto ab74c7f7eb Add select selector for blueprints (#8297) 2021-02-15 10:22:00 +01:00
GitHub Action 6b673c7f44 Translation update 2021-02-15 01:18:50 +00:00
GitHub Action 53510a3cb9 Translation update 2021-02-14 01:19:47 +00:00
GitHub Action d4d38a880d Translation update 2021-02-13 01:17:10 +00:00
GitHub Action 18783d5e3b Translation update 2021-02-12 01:17:41 +00:00
Philip Allgaier eb235cb552 Add bottom margin to button card icon (#8362) 2021-02-11 13:39:31 +01:00
GitHub Action 435a6b6d53 Translation update 2021-02-11 01:17:07 +00:00
GitHub Action 8d13745c6b Translation update 2021-02-10 01:16:43 +00:00
Franck Nijhof 14c7cfc64c Add GitHub Issue Form (#8363) 2021-02-09 18:15:57 +01:00
Joakim Sørensen c7821b9cee Don't show add-on config if no schema (#8361) 2021-02-09 11:51:46 +01:00
GitHub Action a1d66aef0c Translation update 2021-02-09 01:17:05 +00:00
Jaroslav Hanslík e275f1f4b9 Fixed state card of number entity (#8325) 2021-02-08 16:28:28 +01:00
Joakim Sørensen 48de8b0739 Block snapshots when system is not running (#8350) 2021-02-08 16:18:33 +01:00
Joakim Sørensen b75dc0efe0 Fix issue with jumping config (#8355) 2021-02-08 16:18:01 +01:00
Paulus Schoutsen 1d498349c5 Update container port (#8352)
* Update container port

* Update .devcontainer/devcontainer.json

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-02-08 16:09:40 +01:00
Bram Kragten 5cdcec699b Merge branch 'master' into dev 2021-02-08 15:14:36 +01:00
Bram Kragten cd72287d99 Bumped version to 20210208.0 2021-02-08 15:12:42 +01:00
Bram Kragten c8717bfa32 Add my panel (#8349) 2021-02-08 14:48:54 +01:00
GitHub Action 83de75b689 Translation update 2021-02-08 01:17:33 +00:00
Philip Allgaier e5ea762cbc Resolve merge conflict from PR #8121 2021-02-07 16:36:25 +01:00
Philip Allgaier 01df01cd66 Provide stub config for entity-filter (#8121)
* Provide stub config for entity-filter

* "card" option is optional since it has a default

* Search dynamically for stub config entities
2021-02-07 14:38:54 +01:00
Philip Allgaier 2c07a2c825 Correct typo in "find-entities.ts" file name (#8343) 2021-02-07 14:37:35 +01:00
chriss158 c3f50ba0fb Fix no disconnect after 5 minute timeout (#8339) 2021-02-07 14:33:44 +01:00
GitHub Action c04419fd09 Translation update 2021-02-07 01:18:53 +00:00
Paulus Schoutsen 9c7af0dfce Drop margin from cast header (#8331) 2021-02-06 23:00:06 +01:00
GitHub Action b66d14e980 Translation update 2021-02-06 01:15:59 +00:00
GitHub Action 6a553e9554 Translation update 2021-02-05 01:17:26 +00:00
Joakim Sørensen 4273b72d71 Fix issue where schema is null (#8322)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-02-04 14:01:53 +01:00
GitHub Action 9ccfa79199 Translation update 2021-02-04 01:16:30 +00:00
Tobias Sauerwein fe3d22d4f8 Only display current temp when not None (#8316)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-02-03 20:51:28 +01:00
Joakim Sørensen e06642e892 Show the reason why an add-on is not available (#8312)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-02-03 17:35:55 +01:00
Joakim Sørensen 5199e946a1 Fix button layout for addon-info (#8315) 2021-02-03 16:08:29 +01:00
Joakim Sørensen 17aff2f9b8 Move save button to the right (#8314) 2021-02-03 15:58:17 +01:00
Joakim Sørensen f7c7ac44f7 Show eMMC lifetime (#8302) 2021-02-03 15:52:52 +01:00
Joakim Sørensen 62dd0a561e Fix display issue wtih addon-info grid (#8313) 2021-02-03 15:45:01 +01:00
GitHub Action 858eacddea Translation update 2021-02-03 01:23:54 +00:00
Bram Kragten 471bb5169c Bumped version to 20210127.7 2021-02-02 21:24:52 +01:00
Bram Kragten 9d89aa329c Revert "Add icon support to gauge" (#8303)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-02-02 21:23:08 +01:00
Bram Kragten 4e4d8bdc5e Revert "Add icon support to gauge" (#8303)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-02-02 21:17:38 +01:00
GitHub Action a30ec32ac1 Translation update 2021-02-02 01:31:33 +00:00
Joakim Sørensen a9192ae2e1 Force YAML if schema has multiple (#8298) 2021-02-01 13:00:38 +01:00
Mike Morrison 3d4a0b02e5 Fix gaps in history charts (#8293) 2021-02-01 11:52:12 +01:00
GitHub Action 3659cf7c87 Translation update 2021-02-01 01:37:09 +00:00
GitHub Action fbcf35414c Translation update 2021-01-31 01:36:47 +00:00
Bram Kragten d79e5dd8fb Bumped version to 20210127.6 2021-01-30 22:51:57 +01:00
Philip Allgaier 92b116c0da More precise name handling for auto-generated dashboards (#8289)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-30 22:51:46 +01:00
Bram Kragten da3f911deb Fix tts try on ios (#8292) 2021-01-30 22:51:32 +01:00
Philip Allgaier 9d82ce8ab4 Add missing device_classes to sensor (#8288) 2021-01-30 22:51:13 +01:00
Bram Kragten db9597d2e7 Don't use badges in generated Lovelace + group entities by area (#8291) 2021-01-30 22:50:56 +01:00
Philip Allgaier 1523558f4c More precise name handling for auto-generated dashboards (#8289)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-30 22:49:48 +01:00
Bram Kragten bd9f4fe41c Fix tts try on ios (#8292) 2021-01-30 22:42:01 +01:00
Philip Allgaier 6b938b2597 Add missing device_classes to sensor (#8288) 2021-01-30 21:32:25 +01:00
Bram Kragten 5a8009e46e Don't use badges in generated Lovelace + group entities by area (#8291) 2021-01-30 21:30:47 +01:00
GitHub Action c92eeb6bb7 Translation update 2021-01-30 01:33:03 +00:00
Bram Kragten 8ea6baaf5d Bumped version to 20210127.5 2021-01-29 18:38:36 +01:00
Bram Kragten 1ed03842c0 Fix grid + map editor (#8284) 2021-01-29 18:38:25 +01:00
Bram Kragten 0e9984413c Fix grid + map editor (#8284) 2021-01-29 18:37:58 +01:00
Philip Allgaier 362b419814 Add missing extra field translation for cover position (#8273)
* Ensure ha-form-integer passes "0" to form data

* Only keep the translation change
2021-01-29 18:14:07 +01:00
chriss158 bffcccc1fe Fix external auth reconnection loop if connection lost after refresh token expiration (#8279) 2021-01-29 18:13:48 +01:00
Bram Kragten b8e9a4ce9f Fix map editor (#8280) 2021-01-29 18:13:26 +01:00
Bram Kragten bdff3fd452 Z-wave migration tweaks (#8283) 2021-01-29 18:11:25 +01:00
Bram Kragten 1fc51f0087 Bumped version to 20210127.4 2021-01-29 18:10:58 +01:00
Bram Kragten 226013d999 Z-wave migration tweaks (#8283) 2021-01-29 18:10:18 +01:00
Joakim Sørensen 86847263b8 Initial UI config for add-ons (#8271) 2021-01-29 18:06:38 +01:00
Bram Kragten b798523d53 Fix map editor (#8280) 2021-01-29 17:19:01 +01:00
chriss158 bd59c4fccf Fix external auth reconnection loop if connection lost after refresh token expiration (#8279) 2021-01-29 13:40:09 +01:00
GitHub Action 566ffe24a4 Translation update 2021-01-29 01:33:55 +00:00
Philip Allgaier 0e5c1b2041 Add missing extra field translation for cover position (#8273)
* Ensure ha-form-integer passes "0" to form data

* Only keep the translation change
2021-01-28 22:39:45 +01:00
Bram Kragten 9a088a21da Bumped version to 20210127.3 2021-01-28 22:35:49 +01:00
Bram Kragten 1160d27004 Revert "Bumped version to 20210127.2"
This reverts commit 3766f44787.
2021-01-28 22:34:31 +01:00
Bram Kragten b4e5740050 Fix race condition in zwave migration (#8268) 2021-01-28 20:59:54 +01:00
Bram Kragten 12bb3f5796 Use close dialog function to close device registry detail dialog (#8269) 2021-01-28 20:59:37 +01:00
Bram Kragten ff62fdb69d hide config links in demo (#8267) 2021-01-28 20:59:15 +01:00
Bram Kragten 4ebf32cb1f Move try tss button to bottom (#8266) 2021-01-28 20:58:59 +01:00
Thomas Lovén 5afb8a77a9 Make input_text entity row usable when value is "unknown" (#8258) 2021-01-28 20:58:43 +01:00
Jaroslav Hanslík 48ed33af95 Typo in texts (#8265) 2021-01-28 20:58:26 +01:00
Jaroslav Hanslík 4a64cd4464 Typo in texts (#8264) 2021-01-28 20:58:12 +01:00
Paulus Schoutsen 8ae1a1b558 Fix tts (#8261) 2021-01-28 20:57:56 +01:00
Philip Allgaier ef1dd8b761 Add check to prevent undefined access during action validation (#8257) 2021-01-28 20:57:41 +01:00
Bram Kragten 3766f44787 Bumped version to 20210127.2 2021-01-28 20:57:15 +01:00
Bram Kragten 101067d018 Fix race condition in zwave migration (#8268) 2021-01-28 20:20:07 +01:00
Bram Kragten 448d19bfbb Use close dialog function to close device registry detail dialog (#8269) 2021-01-28 20:18:13 +01:00
Bram Kragten c1caad6d43 hide config links in demo (#8267) 2021-01-28 20:16:00 +01:00
Bram Kragten a653bf5b0d Move try tss button to bottom (#8266) 2021-01-28 20:10:15 +01:00
J. Nick Koston afdb369e04 Separate fan speeds into percentages and presets modes (#8216) 2021-01-28 09:24:18 -06:00
Thomas Lovén f02841409c Make input_text entity row usable when value is "unknown" (#8258) 2021-01-28 14:05:07 +01:00
Kendell R d4000cf662 Allow theming of header edit background color (#8246)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-28 12:00:40 +01:00
Jaroslav Hanslík e1cf5919fa Typo in texts (#8265) 2021-01-28 11:55:00 +01:00
Jaroslav Hanslík d33e8d77c2 Typo in texts (#8264) 2021-01-28 11:54:34 +01:00
GitHub Action 3a522215a9 Translation update 2021-01-28 01:34:39 +00:00
Paulus Schoutsen 7de6ea0879 Fix tts (#8261) 2021-01-27 23:42:06 +01:00
Joakim Sørensen 5ee0250ba5 Add hostname and metrics to Add-ons (#8253) 2021-01-27 22:38:07 +01:00
Philip Allgaier 69d0a22091 Add check to prevent undefined access during action validation (#8257) 2021-01-27 18:39:03 +01:00
Philip Allgaier 4d6c11ce31 Enforce "good" states of binary_sensor as green in history timeline chart (#8145) 2021-01-27 17:22:01 +01:00
Bram Kragten 178605664e Bumped version to 20210127.1 2021-01-27 17:17:48 +01:00
Bram Kragten 12acb5473b Bumped version to 20210127.1 2021-01-27 17:17:20 +01:00
Joakim Sørensen 0cf8004b8d Add twine to release flow (#8254) 2021-01-27 17:14:00 +01:00
Joakim Sørensen 6b0a8eae74 Add twine to release flow (#8254) 2021-01-27 17:13:18 +01:00
Bram Kragten 00412c7216 Merge pull request #8252 from home-assistant/dev 2021-01-27 16:24:07 +01:00
Bram Kragten 6483f23558 Merge branch 'master' into dev 2021-01-27 16:10:48 +01:00
Bram Kragten 4234d51a29 Bumped version to 20210127.0 2021-01-27 16:09:35 +01:00
Bram Kragten 9243d300cc Zwave -> OZW migration (#7765)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
2021-01-27 16:01:18 +01:00
Thomas Lovén 2ce70206c6 Markdown format blueprint descriptions in import dialog (#8251) 2021-01-27 16:00:15 +01:00
Bram Kragten b22455d2a5 Add simple try tts dialog (#8245) 2021-01-27 15:58:58 +01:00
Philip Allgaier 4f0bb9f6c3 Add icon support to gauge (#8081) 2021-01-27 13:36:03 +01:00
Philip Allgaier 7dfa1b0942 Convert state history chart to LitElement + add warning if history is disabled (#7994)
Co-authored-by: Ian Richardson <iantrich@gmail.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-27 13:13:40 +01:00
Philip Allgaier 093e23c006 Auto capitalize first letter of entity row name (#7962) 2021-01-27 12:38:39 +01:00
Philip Allgaier 5f003ccbe2 Add navigation to dashboard & resources to lovelace editor + icons (#8156) 2021-01-27 12:37:28 +01:00
Philip Allgaier 8d0608610f Entity registry: Translate advanced section + direct link to entity customization (#8181) 2021-01-27 12:35:25 +01:00
Philip Allgaier 8bfe583a20 Convert customize to LitElement + "real" entity picker + option to directly jump to entity (#8180)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-27 12:34:31 +01:00
Philip Allgaier 0a3172dfdb More comprehensive YAML config errors + dynamic checks for action configs (#8217) 2021-01-27 12:33:57 +01:00
Thomas Lovén 5c0e151bc2 Add selectors for text and arbitrary objects (#8152) 2021-01-27 11:45:51 +01:00
Ian Richardson e69f36047d add actions to weather-forecast-card (#7659) 2021-01-27 10:59:09 +01:00
Philip Allgaier ed368ddd9d Add support for delay values split into parts + millisecond time input (#7886)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-27 10:46:15 +01:00
GitHub Action 8400e90e34 Translation update 2021-01-27 01:32:26 +00:00
Paulus Schoutsen 4cd95b724b Use minified bundle of node vibrant (#7784)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-26 23:24:23 +01:00
Shulyaka 46b3836fbd Add number entity support (#7876) 2021-01-26 23:14:23 +01:00
Tim McCormick 9988227d93 Don't assume position of items in array (#7927) 2021-01-26 22:59:58 +01:00
Bram Kragten 9d289bfa34 Add download dump button to zwave_js panel (#8232)
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
2021-01-26 22:20:16 +01:00
Bram Kragten 048de6b388 Fix translation action (#8244) 2021-01-26 17:21:38 +01:00
Philip Allgaier 9b7d8934da Correctly handle "hours to show" for footer graph (#8071)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-26 16:15:32 +01:00
Philip Allgaier 0dd9b21c2d Add minutes to hourly weather forecast time display (#8182)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-26 16:14:34 +01:00
Philip Allgaier 417184525f Adjust calendar translations to fix loading issue in calendar card (#8235) 2021-01-26 10:42:13 +01:00
Philip Allgaier 0a09ec706f Visual alignment of automation and script more info dialogs (#8234) 2021-01-26 10:25:34 +01:00
Philip Allgaier cf43b26e14 Prevent automation action row issue if event name gets cleared (#8213)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-25 20:01:30 +01:00
Philip Allgaier a8b27e224f Allow themeing of media control card (#8209)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-25 20:00:50 +01:00
Philip Allgaier dc5d14834d Added missing translations for some automation extra-fields (#8210) 2021-01-25 11:59:44 +01:00
Philip Allgaier 12c935f647 Show entity_picture in logbook (#8118) 2021-01-25 11:48:16 +01:00
Philip Allgaier 7710cb245c Use correct color for shopping list card section header (#8223) 2021-01-25 11:35:04 +01:00
b3nj1 a6b77c0457 entity-filter card allow state_filter.value == 0 (number 0) (#8225) 2021-01-25 10:54:22 +01:00
Philip Allgaier 748a05f355 Use app header color as default selected tab color + allow styling (#8227) 2021-01-25 10:53:53 +01:00
Philip Allgaier 831b9da0cf Various demo tweaks + cleanups (#8229) 2021-01-25 10:52:59 +01:00
Charles Garwood a3339c9d5f Add wizards for adding and removing Z-Wave JS nodes (#8174) 2021-01-25 10:46:50 +01:00
Philip Allgaier d228f38471 Correct URL to automation run modes documentation (#8230) 2021-01-25 01:51:00 +01:00
Philip Allgaier fe13853b8b Allow timestamp attribute formatting + central timestamp formats (#8162) 2021-01-22 20:24:31 +01:00
Philip Allgaier 6f4dbdc959 Improve entity registry errors display (#8208) 2021-01-22 15:50:11 +01:00
Bram Kragten 870f0bcbb1 Fix some dialog close history issues (#8102)
Co-authored-by: Zack Barett <arnett.zackary@gmail.com>
Co-authored-by: Philip Allgaier <mail@spacegaier.de>
2021-01-22 13:33:10 +01:00
Philip Allgaier 599dd81e3c Make "button row" name optional to be consistent with "button card" and "buttons row" (#8189) 2021-01-21 20:03:53 +01:00
Philip Allgaier 9e99d158fd Add error handling to action handling (#8187) 2021-01-21 19:38:19 +01:00
Philip Allgaier 136ebb5a07 Set sensible default tap_action (same logic as button row) (#8186) 2021-01-21 16:44:17 +01:00
Charles Garwood 707338b1aa Add Z-Wave info to device page for zwave_js devices (#8195) 2021-01-21 16:16:24 +01:00
Bram Kragten 7e06bd53b6 Change remote ui portal to account page (#8193) 2021-01-18 16:47:14 +01:00
Joakim Sørensen 08c1b864fc Fix missing addon list when creating partial snapshot (#8176) 2021-01-17 17:34:14 +01:00
Thomas Lovén 16e7a16d12 Fix date picker text color (#7776) 2021-01-16 20:48:18 +01:00
Thomas Lovén 45200da32f Helpful comment. See #8152 (#8160) 2021-01-16 16:40:59 +01:00
Philip Allgaier 84a9ca59ef Add "columns" and "square" to grid card editor (#7888) 2021-01-16 16:25:13 +01:00
Charles Garwood 40f4c35b42 Initial Z-Wave JS Config Panel (#8166) 2021-01-16 16:22:27 +01:00
Philip Allgaier bb77d34017 Tweaks to cloud integration page (#7878) 2021-01-16 16:19:57 +01:00
Philip Allgaier 9659ebe59b Increase font size for alarm keypad number buttons (#8120) 2021-01-16 16:09:52 +01:00
Thomas Lovén 16c914b139 Show default value for selector-less blueprint inputs. (#8163) 2021-01-15 14:30:27 +01:00
Philip Allgaier 142f26add1 Align entities card section header styling (#8113) 2021-01-14 13:05:37 +01:00
Philip Allgaier ef7d2aea8d Fix config update handling for map card editor (#8115) 2021-01-14 12:52:58 +01:00
Philip Allgaier 1aa40cb6df Convert state-card-display to LitElement and use timestamp display (#8150)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-14 12:49:29 +01:00
Philip Allgaier cd06b931a9 Allow empty/undefined for counter min & max (#8154) 2021-01-14 12:25:06 +01:00
Mick Vleeshouwer fd2df92000 Change supportsStop to supportsStopTilt (#8153) 2021-01-14 12:24:26 +01:00
Philip Allgaier b565a8b8f7 Allow "idle" media players to be switched of in entity row (#8155) 2021-01-14 11:35:31 +01:00
Philip Allgaier 1aab656705 Make entity-filter and glances demo a bit clearer & cleaner (#8157) 2021-01-14 11:20:18 +01:00
Philip Allgaier 6919d0cde6 Ensure we always show backend error in automation and script editor (#8139)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-13 20:45:40 +01:00
Philip Allgaier 95fd8de1bc Adjust person / presence detection texts (#8117) 2021-01-13 20:13:37 +01:00
Philip Allgaier 28ca1a5193 Adjust / theme entity marker background color (#8116) 2021-01-13 17:40:15 +01:00
Joakim Sørensen a1d07e5a00 Sort imports (#8104) 2021-01-13 17:17:12 +01:00
Philip Allgaier 131a7f3782 Clear entity ID during automation duplication (#8140) 2021-01-13 17:12:51 +01:00
Philip Allgaier c28a7d6c10 Center align content of date input (#8134) 2021-01-13 17:10:08 +01:00
J. Nick Koston 6b20bb967b Add support for dhcp discovery (#8149) 2021-01-13 11:07:32 +01:00
Paulus Schoutsen 75f228418d Allow configuring default TTS voice for cloud (#8148) 2021-01-13 11:07:03 +01:00
Joakim Sørensen 41f8b0d19b Add verify-version step to release (#8138) 2021-01-12 10:32:38 +01:00
Paulus Schoutsen cea3b8b010 Show name on ignored entries (#8135) 2021-01-12 09:26:31 +01:00
Nikfinn99 aba0e1f026 Automation - State Condition display 'for:' in frontend (#8124)
* Automation - State Condition display 'for:' in frontend

* rename variable to better name
2021-01-11 17:24:39 +01:00
Philip Allgaier f42587af22 Format URL attribute as link (#8126)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-01-11 15:59:26 +01:00
Joakim Sørensen edcb7e87bb Hide configuration card if the add-on does not expose options or schema (#8131) 2021-01-11 14:43:24 +01:00
Joakim Sørensen 96f0ceeb8c Update supervisor on add-on store refresh (#8132) 2021-01-11 14:42:58 +01:00
Joakim Sørensen a9baa7f1c1 Move netlify preview builds from Azure to Github (#8105)
* Move netlify preview builds from Azure  to Github

* Remove checkout

* Update secret names
2021-01-11 14:03:21 +01:00
Joakim Sørensen b1483287dc Add Core to supervisor/system (#8123) 2021-01-11 13:51:52 +01:00
Thomas Lovén a4657541fc Show location name in auth page, if available. (#8119) 2021-01-11 13:11:30 +01:00
Pikles 56d88b4c56 Add CSS Variables for Menu (#7917) 2021-01-11 12:50:33 +01:00
Joakim Sørensen 02313e4be8 Move translations from Azure to Github (#8129)
* Move translations from Azure to Github

* remove [ci skip]
2021-01-11 12:27:10 +01:00
Joakim Sørensen 2a14a3a4dc Move release builds from Azure to Github (#8128)
* Move release builds from Azure to Github

* Fix syntax issue (YAML is hard)
2021-01-11 12:14:53 +01:00
Philip Allgaier d02a2e8c2e Consistently use isComponentLoaded() helper (#7995) 2021-01-10 19:52:22 +01:00
Bram Kragten 0d281f8437 Fix for undefined stateObj in media player row (#8114) 2021-01-08 09:39:55 -06:00
Thomas Lovén 2b0f43f334 Format blueprint descriptions with markdown (#8109) 2021-01-08 12:22:12 +01:00
Philip Allgaier f9d28fc124 Add blank between temperature and unit in weather card (#8111) 2021-01-08 11:10:39 +01:00
Philip Allgaier da07173471 Prevent YAML mode if no entity is set in card editor (#8110) 2021-01-08 11:07:04 +01:00
Joakim Sørensen 4deeff7029 Prefix supervisor version with supervisor- (#8107) 2021-01-08 10:54:12 +01:00
Bram Kragten a9d926e80a Bumped version to 20201229.1 2021-01-05 20:38:52 +01:00
Philip Allgaier c41369c89c Add EN fallback text for dismiss button (#8068) 2021-01-05 20:38:36 +01:00
Philip Allgaier 656bef3da9 Prevent relative time text wrapping in more-info-sun (#8051) 2021-01-05 20:38:18 +01:00
Bram Kragten 3d7ee6a4df Merge pull request #8048 from home-assistant/dev 2020-12-29 23:23:58 +01:00
593 changed files with 18939 additions and 5925 deletions
+4 -1
View File
@@ -4,7 +4,7 @@
"dockerfile": "Dockerfile",
"context": ".."
},
"appPort": 8123,
"appPort": "8124:8123",
"context": "..",
"postCreateCommand": "script/bootstrap",
"extensions": [
@@ -26,6 +26,9 @@
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.trimTrailingWhitespace": true
}
}
@@ -77,8 +77,8 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
## Problem-relevant frontend configuration
<!--
An example configuration that caused the problem for you, e.g. the YAML configuration
of the used cards. Fill this out even if it seems unimportant to you. Please be sure
An example configuration that caused the problem for you, e.g. the YAML configuration
of the used cards. Fill this out even if it seems unimportant to you. Please be sure
to remove personal information like passwords, private URLs and other credentials.
-->
+138
View File
@@ -0,0 +1,138 @@
name: Report a bug with the UI, Frontend or Lovelace
about: Report an issue related to the Home Assistant frontend.
labels: bug
title: ""
issue_body: true
body:
- type: markdown
attributes:
value: |
Make sure you are running the [latest version of Home Assistant][releases] before reporting an issue.
If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue.
**Please not not report issues for custom Lovelace cards.**
[fr]: https://github.com/home-assistant/frontend/discussions
[releases]: https://github.com/home-assistant/home-assistant/releases
- type: checkboxes
attributes:
label: Checklist
description: Please verify that you've followed these steps
options:
- label: I have updated to the latest available Home Assistant version.
required: true
- label: I have cleared the cache of my browser.
required: true
- label: I have tried a different browser to see if it is related to my browser.
required: true
- type: markdown
attributes:
value: |
## The problem
- type: textarea
validations:
required: true
attributes:
label: Describe the issue you are experiencing
description: Provide a clear and concise description of what the bug is.
- type: textarea
validations:
required: true
attributes:
label: Describe the behavior you expected
description: Describe what you expected to happen or it should look/behave.
- type: textarea
validations:
required: true
attributes:
label: Steps to reproduce the issue
description: |
Please tell us exactly how to reproduce your issue.
Provide clear and concise step by step instructions and add code snippets if needed.
value: |
1.
2.
3.
...
- type: markdown
attributes:
value: |
## Environment
- type: input
validations:
required: true
attributes:
label: What version of Home Assistant Core has the issue?
placeholder: core-
description: >
Can be found in the Configuration panel -> Info.
- type: input
attributes:
label: What was the last working version of Home Assistant Core?
placeholder: core-
description: >
If known, otherwise leave blank.
- type: input
attributes:
label: In which browser are you experiencing the issue with?
placeholder: Google Chrome 88.0.4324.150
description: >
Provide the full name and don't forget to add the version!
- type: input
attributes:
label: Which operating system are you using to run this browser?
placeholder: macOS Big Sur (1.11)
description: >
Don't forget to add the version!
- type: markdown
attributes:
value: |
# Details
- type: textarea
attributes:
label: State of relevant entities
description: >
If your issue is about how an entity is shown in the UI, please add the
state and attributes for all situations. You can find this information
at Developer Tools -> States.
value: |
```yaml
# Paste your state here.
```
- type: textarea
attributes:
label: Problem-relevant frontend configuration
description: >
An example configuration that caused the problem for you, e.g., the YAML
configuration of the used cards. Fill this out even if it seems
unimportant to you. Please be sure to remove personal information like
passwords, private URLs and other credentials.
value: |
```yaml
# Paste your YAML here.
```
- type: textarea
attributes:
label: Javascript errors shown in your browser console/inspector
description: >
If you come across any Javascript or other error logs, e.g., in your
browser console/inspector please provide them.
value: |
```txt
# Paste your logs here.
```
- type: markdown
attributes:
value: |
## Additional information
- type: markdown
attributes:
value: |
If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here,
by dragging and dropping files in the field below.
+19
View File
@@ -0,0 +1,19 @@
name: Netlify
on:
schedule:
- cron: "0 0 * * *"
jobs:
trigger_builds:
name: Trigger netlify build preview
runs-on: "ubuntu-latest"
steps:
- name: Trigger Cast build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }}
- name: Trigger Demo build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }}
- name: Trigger Gallery build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }}
+81
View File
@@ -0,0 +1,81 @@
name: Release
on:
release:
types:
- published
env:
WHEELS_TAG: 3.7-alpine3.11
PYTHON_VERSION: 3.7
NODE_VERSION: 12.1
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Build and release package
run: |
python3 -m pip install twine
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
script/release
wheels-init:
name: Init wheels build
needs: release
runs-on: ubuntu-latest
steps:
- name: Generate requirements.txt
run: |
# Sleep to give pypi time to populate the new version across mirrors
sleep 240
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Upload requirements.txt
uses: actions/upload-artifact@v2
with:
name: requirements
path: ./requirements.txt
build-wheels:
name: Build wheels for ${{ matrix.arch }}
needs: wheels-init
runs-on: ubuntu-latest
strategy:
matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
steps:
- name: Download requirements.txt
uses: actions/download-artifact@v2
with:
name: requirements
- name: Build wheels
uses: home-assistant/wheels@master
with:
tag: ${{ env.WHEELS_TAG }}
arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
requirements: "requirements.txt"
+65
View File
@@ -0,0 +1,65 @@
name: Translations
on:
schedule:
- cron: "30 0 * * *"
push:
branches:
- dev
paths:
- translations/en.json
env:
NODE_VERSION: 12
jobs:
upload:
name: Upload
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: Upload Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base
download:
name: Download
needs: upload
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
npm install
./script/translations_download
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
with:
name: GitHub Action
email: github-action@users.noreply.github.com
- name: Update translation
run: |
git add translations
git commit -am "Translation update"
git push
-30
View File
@@ -1,30 +0,0 @@
# https://dev.azure.com/home-assistant
trigger: none
pr: none
schedules:
- cron: "0 0 * * *"
displayName: "build preview"
branches:
include:
- dev
always: true
variables:
- group: netlify
jobs:
- job: 'Netlify_preview'
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
# Cast
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_CAST}
# Demo
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO}
# Gallery
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_GALLERY}
displayName: 'Trigger netlify build preview'
-59
View File
@@ -1,59 +0,0 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
tags:
include:
- "*"
pr: none
variables:
- name: versionWheels
value: '1.10.1-3.7-alpine3.11'
- name: versionNode
value: '12.1'
- group: twine
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
stages:
- stage: "Validate"
jobs:
- template: templates/azp-job-version.yaml@azure
- stage: "Build"
jobs:
- job: "ReleasePython"
pool:
vmImage: "ubuntu-latest"
steps:
- task: UsePythonVersion@0
displayName: "Use Python 3.7"
inputs:
versionSpec: "3.7"
- task: NodeTool@0
displayName: "Use Node $(versionNode)"
inputs:
versionSpec: "$(versionNode)"
- script: pip install twine wheel
displayName: "Install tools"
- script: |
export TWINE_USERNAME="$(twineUser)"
export TWINE_PASSWORD="$(twinePassword)"
script/release
displayName: "Build and release package"
- stage: "Wheels"
jobs:
- template: templates/azp-job-wheels.yaml@azure
parameters:
builderVersion: '$(versionWheels)'
wheelsRequirement: 'requirement.txt'
preBuild:
- script: |
sleep 240
echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt
-70
View File
@@ -1,70 +0,0 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
branches:
include:
- dev
paths:
include:
- translations/en.json
pr: none
schedules:
- cron: "30 0 * * *"
displayName: "frontend translation update"
branches:
include:
- dev
always: true
variables:
- group: translation
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
jobs:
- job: 'Upload'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: '12.x'
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
./script/translations_upload_base
displayName: 'Upload Translation'
- job: 'Download'
dependsOn:
- 'Upload'
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: '12.x'
- template: templates/azp-step-git-init.yaml@azure
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
npm install
./script/translations_download
displayName: 'Download Translation'
- script: |
git checkout dev
git add translation
git commit -am "[ci skip] Translation update"
git push
displayName: 'Update translation'
+1 -1
View File
@@ -48,7 +48,7 @@ class HcCast extends LitElement {
protected render(): TemplateResult {
if (this.lovelaceConfig === undefined) {
return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `;
return html`<hass-loading-screen no-toolbar></hass-loading-screen>`;
}
const error =
+5 -1
View File
@@ -98,8 +98,12 @@ class HcLayout extends LitElement {
line-height: 32px;
padding: 24px 16px 16px;
display: block;
margin: 0;
}
.hero {
border-radius: 4px 4px 0 0;
}
.subtitle {
font-size: 14px;
color: var(--secondary-text-color);
+1 -1
View File
@@ -1,8 +1,8 @@
import {
customElement,
html,
property,
internalProperty,
property,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../../demo/src/stubs/history";
+1 -1
View File
@@ -1,4 +1,4 @@
import "web-animations-js/web-animations-next-lite.min";
import "../../../src/resources/roboto";
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "./layout/hc-lovelace";
+6 -2
View File
@@ -54,6 +54,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "21",
attributes: {
friendly_name: "Living room temperature",
device_class: "temperature",
unit_of_measurement: "°C",
},
},
"sensor.study_temp_rounded": {
@@ -61,6 +63,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "23",
attributes: {
friendly_name: "Study temperature",
device_class: "temperature",
unit_of_measurement: "°C",
},
},
"sensor.living_room": {
@@ -261,7 +265,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
entity_id: "light.kitchen_lights",
state: "off",
attributes: {
friendly_name: "Kitchen lights",
friendly_name: "Kitchen Lights",
supported_features: 1,
},
},
@@ -484,7 +488,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
attributes: {
min_mireds: 111,
max_mireds: 400,
friendly_name: "Garage lights",
friendly_name: "Garage Lights",
supported_features: 55,
},
},
+1
View File
@@ -12,6 +12,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
{
type: "entities",
title: localize("ui.panel.page-demo.config.arsaboo.labels.lights"),
state_color: true,
entities: [
{
entity: "light.kitchen_lights",
+2 -2
View File
@@ -653,7 +653,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.smoke_sensor_158d0001b8ddc7",
state: "off",
attributes: {
Density: 0,
density: 0,
battery_level: 59,
friendly_name: "Downstairs Smoke Detector",
device_class: "smoke",
@@ -663,7 +663,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.smoke_sensor_158d0001b8deba",
state: "off",
attributes: {
Density: 0,
density: 0,
battery_level: 65,
friendly_name: "Upstairs Smoke Detector",
device_class: "smoke",
+6 -181
View File
@@ -3,49 +3,7 @@ import { DemoConfig } from "../types";
export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
name: "Kingia Castle",
resources: [
// {
// url: "/local/custom_ui/dark-sky-weather-card.js?v=4",
// type: "js",
// },
// {
// url: "/local/custom_ui/mini-media-player-bundle.js?v=0.9.8",
// type: "module",
// },
// {
// url: "/local/custom_ui/tracker-card.js?v=0.1.5",
// type: "js",
// },
// {
// url: "/local/custom_ui/surveillance-card.js?v=0.0.1",
// type: "module",
// },
// {
// url: "/local/custom_ui/mini-graph-card-bundle.js?v=0.1.0",
// type: "module",
// },
// {
// url: "/local/custom_ui/slider-entity-row.js?v=d6da75",
// type: "js",
// },
// {
// url:
// "/local/custom_ui/compact-custom-header/compact-custom-header.js?v=0.2.7",
// type: "js",
// },
// {
// url: "/local/custom_ui/waze-card.js?v=1.1.1",
// type: "js",
// },
// {
// url: "/local/custom_ui/circle-sensor-card.js?v=1.2.0",
// type: "module",
// },
// {
// url: "/local/custom_ui/monster-card.js?v=0.2.3",
// type: "js",
// },
],
resources: [],
views: [
{
cards: [
@@ -603,89 +561,6 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
},
{
cards: [
// {
// style: {
// "background-image": 'url("/assets/jimpower/cardbackK.png")',
// "background-size": "100% 400px",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// "background-repeat": "no-repeat",
// color: "#999999",
// "border-radius": "20px",
// border: "solid 1px rgba(100,100,100,0.3)",
// "background-color": "rgba(50,50,50,0.3)",
// },
// type: "custom:card-modder",
// card: {
// entity_visibility: "sensor.dark_sky_visibility",
// entity_sun: "sun.sun",
// entity_daily_summary:
// "sensor.bom_gc_forecast_detailed_summary_0",
// entity_temperature: "sensor.bom_temp",
// entity_forecast_high_temp_3:
// "sensor.bom_gc_forecast_max_temp_c_3",
// entity_forecast_high_temp_2:
// "sensor.bom_gc_forecast_max_temp_c_2",
// entity_forecast_high_temp_5:
// "sensor.bom_gc_forecast_max_temp_c_5",
// entity_forecast_high_temp_4:
// "sensor.bom_gc_forecast_max_temp_c_4",
// entity_wind_speed: "sensor.bom_wind_sp",
// entity_forecast_icon_4: "sensor.dark_sky_icon_4",
// entity_forecast_icon_5: "sensor.dark_sky_icon_5",
// entity_forecast_icon_2: "sensor.dark_sky_icon_2",
// entity_forecast_icon_3: "sensor.dark_sky_icon_3",
// entity_forecast_icon_1: "sensor.dark_sky_icon_1",
// entity_forecast_high_temp_1:
// "sensor.bom_gc_forecast_max_temp_c_1",
// entity_wind_bearing: "sensor.bom_wind_bear",
// entity_forecast_low_temp_2:
// "sensor.bom_gc_forecast_min_temp_c_2",
// entity_forecast_low_temp_3:
// "sensor.bom_gc_forecast_min_temp_c_3",
// entity_pressure: "sensor.bom_pres",
// entity_forecast_low_temp_1:
// "sensor.bom_gc_forecast_min_temp_c_1",
// entity_forecast_low_temp_4:
// "sensor.bom_gc_forecast_min_temp_c_4",
// entity_forecast_low_temp_5:
// "sensor.bom_gc_forecast_min_temp_c_5",
// entity_humidity: "sensor.bom_humd",
// type: "custom:dark-sky-weather-card",
// entity_current_conditions: "sensor.dark_sky_icon",
// },
// },
// {
// style: {
// "background-image": 'url("/assets/jimpower/home/waze_5.png")',
// "background-size": "100% 400px",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// "background-repeat": "no-repeat",
// "border-radius": "20px",
// border: "solid 1px rgba(100,100,100,0.3)",
// "background-color": "rgba(50,50,50,0.3)",
// },
// type: "custom:card-modder",
// card: {
// entities: [
// {
// name: "James",
// zone: "zone.home",
// entity: "sensor.james_to_home",
// },
// {
// name: "Tina",
// zone: "zone.home",
// entity: "sensor.tina_to_home",
// },
// {
// name: "Work",
// zone: "zone.powertec",
// entity: "sensor.commute_to_work",
// },
// ],
// type: "custom:waze-card",
// },
// },
{
style: {
"border-radius": "20px",
@@ -722,46 +597,8 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
],
type: "vertical-stack",
},
// {
// cards: [
// {
// style: {
// "border-radius": "20px",
// color: "#999999",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// border: "solid 1px rgba(100,100,100,0.3)",
// },
// type: "custom:card-modder",
// card: {
// type: "picture-entity",
// entity: "camera.bom_radar",
// },
// },
// // {
// // style: {
// // "background-image": 'url("/assets/jimpower/cardbackK.png")',
// // "background-size": "100% 525px",
// // "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// // "background-repeat": "no-repeat",
// // color: "#999999",
// // "border-radius": "20px",
// // border: "solid 1px rgba(100,100,100,0.3)",
// // "background-color": "rgba(50,50,50,0.3)",
// // },
// // type: "custom:card-modder",
// // card: {
// // title: null,
// // type: "custom:tracker-card",
// // trackers: [
// // "sensor.custom_card_tracker",
// // "sensor.custom_component_tracker",
// // ],
// // },
// // },
// ],
// type: "vertical-stack",
// },
],
path: "home",
icon: "mdi:castle",
name: "Home",
background:
@@ -881,26 +718,13 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
card: {
image: "/assets/jimpower/security/air_8.jpg",
elements: [
{
image:
"https://www.airvisual.com/assets/aqi/ic-face-1-green.svg",
type: "image",
style: {
width: "80px",
top: "30%",
left: "12%",
transform: "none",
height: "80px",
},
entity: "sensor.us_air_pollution_level_2",
},
{
style: {
color: "hsl(120, 41%, 39%)",
top: "50%",
"font-weight": 600,
"font-size": "20px",
left: "44%",
"font-size": "50px",
left: "30%",
},
type: "state-label",
entity: "sensor.us_air_pollution_level_2",
@@ -920,7 +744,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
style: {
color: "white",
top: "80%",
left: "52%",
left: "48%",
},
type: "state-icon",
entity: "sensor.us_main_pollutant_2",
@@ -1411,6 +1235,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
type: "vertical-stack",
},
],
path: "security",
icon: "hass:shield-home",
name: "Security",
background:
+10 -5
View File
@@ -101,7 +101,12 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
"sensor.zwave_battery_front_door": {
entity_id: "sensor.zwave_battery_front_door",
state: "63",
attributes: { friendly_name: "Battery", icon: "mdi:battery-60" },
attributes: {
friendly_name: "Battery",
icon: "mdi:battery-60",
unit_of_measurement: "%",
device_class: "battery",
},
},
"sensor.oskar_devices": {
entity_id: "sensor.oskar_devices",
@@ -164,7 +169,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
},
"input_select.christmas_pattern": {
entity_id: "input_select.christmas_pattern",
state: "None",
state: "Rainbow",
attributes: {
options: [
"None",
@@ -186,7 +191,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
},
"input_select.christmas_palette": {
entity_id: "input_select.christmas_palette",
state: "None",
state: "Party",
attributes: {
options: [
"None",
@@ -457,7 +462,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "0.0",
attributes: {
unit_of_measurement: "kB/s",
friendly_name: "Nedladdning",
friendly_name: "Downloading",
icon: "mdi:file-download",
},
},
@@ -471,7 +476,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "0.0",
attributes: {
unit_of_measurement: "kB/s",
friendly_name: "Uppladdning",
friendly_name: "Uploading",
icon: "mdi:file-upload",
},
},
+9 -198
View File
@@ -2,44 +2,7 @@ import { DemoConfig } from "../types";
export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
name: "Hem",
resources: [
// {
// url: "/local/custom-lovelace/monster-card.js",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/mini-media-player-bundle.js?v=0.9.8",
// type: "module",
// },
// {
// url: "/local/custom-lovelace/slideshow-card.js?=1.1.0",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/fold-entity-row.js?v=3ae2c4",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/swipe-card/swipe-card.js?v=2.0.0",
// type: "module",
// },
// {
// url: "/local/custom-lovelace/upcoming-media-card/upcoming-media-card.js",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/tracker-card.js?v=0.1.5",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/card-tools.js?v=6ce5d0",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/krisinfo.js?=0.0.1",
// type: "js",
// },
],
resources: [],
views: [
{
cards: [
@@ -64,7 +27,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
style: {
color: "white",
top: "93%",
left: "90%",
left: "85%",
},
type: "state-label",
entity: "sensor.battery_oskar",
@@ -87,7 +50,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{
style: {
color: "white",
top: "92%",
top: "93%",
left: "20%",
},
type: "state-label",
@@ -96,8 +59,8 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{
style: {
color: "white",
top: "92%",
left: "90%",
top: "93%",
left: "85%",
},
type: "state-label",
entity: "sensor.battery_bella",
@@ -105,7 +68,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{
style: {
color: "white",
top: "92%",
top: "93%",
left: "55%",
},
type: "state-label",
@@ -131,78 +94,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
type: "entities",
title: "Lock",
},
// {
// filter: {
// exclude: [
// {
// state: "not_home",
// },
// ],
// include: [
// {
// entity_id: "device_tracker.annasiphone",
// },
// {
// entity_id: "device_tracker.iphone_2",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "G\u00e4ster",
// },
// show_empty: false,
// },
// {
// filter: {
// exclude: [
// {
// state: "Inget",
// },
// {
// state: "i.u.",
// },
// ],
// include: [
// {
// entity_id: "sensor.pollen_al",
// },
// {
// entity_id: "sensor.pollen_alm",
// },
// {
// entity_id: "sensor.pollen_salg_vide",
// },
// {
// entity_id: "sensor.pollen_bjork",
// },
// {
// entity_id: "sensor.pollen_bok",
// },
// {
// entity_id: "sensor.pollen_ek",
// },
// {
// entity_id: "sensor.pollen_grabo",
// },
// {
// entity_id: "sensor.pollen_gras",
// },
// {
// entity_id: "sensor.pollen_hassel",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "Pollenniv\u00e5er",
// },
// show_empty: false,
// },
{
cards: [
{
@@ -226,10 +117,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
],
type: "vertical-stack",
},
// {
// url: "https://embed.windy.com/embed2.html",
// type: "iframe",
// },
{
entities: [
{
@@ -263,6 +150,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
],
type: "glance",
show_state: false,
columns: 4,
},
{
entities: ["sensor.oskar_bluetooth"],
@@ -270,32 +158,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
type: "entities",
title: "Occupancy",
},
// {
// filter: {
// exclude: [
// {
// state: false,
// },
// ],
// include: [
// {
// entity_id:
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_2",
// },
// {
// entity_id:
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_3",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "Brandvarnare",
// },
// show_empty: false,
// },
{
type: "weather-forecast",
entity: "weather.smhi_vader",
@@ -378,41 +240,9 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
"binary_sensor.windows_server",
"binary_sensor.teamspeak",
"binary_sensor.harmony_hub",
// {
// style: {
// height: "1px",
// width: "85%",
// "margin-left": "auto",
// background: "#62717b",
// "margin-right": "auto",
// },
// type: "divider",
// },
// {
// items: ["sensor.uptime_router", "sensor.installerad_routeros"],
// head: {
// entity: "binary_sensor.router",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "mdi:router",
// },
// },
// {
// items: [
// "sensor.uptime_router_server",
// "sensor.installerad_routeros_server",
// ],
// head: {
// entity: "binary_sensor.router_server",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "mdi:router",
// },
// },
],
show_header_toggle: false,
state_color: true,
type: "entities",
title: "Network",
},
@@ -422,29 +252,10 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
"binary_sensor.ubiquiti_switch",
"binary_sensor.ubiquiti_nvr",
"binary_sensor.entre_kamera",
// {
// items: ["sensor.uptime_ap_1"],
// head: {
// entity: "binary_sensor.accesspunkt_1",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "router-wireless",
// },
// },
// {
// items: ["sensor.uptime_ap_2"],
// head: {
// entity: "binary_sensor.accesspunkt_2",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "router-wireless",
// },
// },
"sensor.total_clients_wireless",
],
show_header_toggle: false,
state_color: true,
type: "entities",
title: "Ubiquiti",
},
+2 -66
View File
@@ -215,6 +215,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
card: {
type: "glance",
show_state: false,
columns: 4,
},
state_filter: ["on"],
},
@@ -808,67 +809,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
],
type: "vertical-stack",
},
// {
// cards: [
// {
// entities: [
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Bedside",
// entity: "light.bedside_lamp",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Bedroom",
// entity: "light.bedroom_ceiling_light",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Isa",
// entity: "light.isa_ceiling_light",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Upstairs hallway",
// entity: "light.upstairs_hallway_ceiling_light_level",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Nightlight",
// entity: "light.gateway_light_34ce008bfc4b",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Walk in closet",
// entity: "light.walk_in_closet_lights",
// },
// {
// hide_when_off: true,
// toggle: false,
// type: "custom:slider-entity-row",
// name: "Stefan",
// entity: "light.stefan_lightstrip",
// },
// ],
// show_header_toggle: false,
// type: "entities",
// title: "Upstairs",
// },
// ],
// type: "vertical-stack",
// },
],
path: "lights",
title: "Lights",
@@ -918,10 +858,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "Dafang",
icon: "mdi:webcam",
},
{
name: "IR Hallway",
entity: "sensor.system_ir_blaster",
},
{
name: "IR Bedroom",
entity: "sensor.system_ir_blaster_bedroom",
@@ -940,7 +876,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
"sensor.system_ring_chime",
],
type: "glance",
columns: 5,
columns: 4,
show_state: false,
},
{
+1 -1
View File
@@ -3,8 +3,8 @@ import {
CSSResult,
customElement,
html,
LitElement,
internalProperty,
LitElement,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../src/cast/cast_manager";
+1 -1
View File
@@ -3,9 +3,9 @@ import {
css,
CSSResult,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { until } from "lit-html/directives/until";
+1 -1
View File
@@ -1,8 +1,8 @@
import "../../src/resources/safari-14-attachshadow-patch";
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style";
import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch";
import "./ha-demo";
/* polyfill for paper-dropdown */
+1
View File
@@ -1,3 +1,4 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate";
+3 -3
View File
@@ -2,10 +2,10 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-switch";
import "../../../src/components/ha-formfield";
import "./demo-card";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch";
import "./demo-card";
class DemoCards extends PolymerElement {
static get template() {
+1 -1
View File
@@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card";
import "../../../src/state-summary/state-card-content";
import "../../../src/dialogs/more-info/more-info-content";
import "../../../src/state-summary/state-card-content";
class DemoMoreInfo extends PolymerElement {
static get template() {
+3 -3
View File
@@ -2,10 +2,10 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-switch";
import "../../../src/components/ha-formfield";
import "./demo-more-info";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch";
import "./demo-more-info";
class DemoMoreInfos extends PolymerElement {
static get template() {
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
+1 -1
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -53,7 +53,7 @@ const CONFIGS = [
config: `
- type: button
entity: light.bed_light
tap_action:
tap_action:
action: toggle
`,
},
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -48,7 +48,7 @@ const ENTITIES = [
const CONFIGS = [
{
heading: "Controller",
heading: "Unfiltered controller",
config: `
- type: entities
entities:
@@ -58,7 +58,7 @@ const CONFIGS = [
`,
},
{
heading: "Basic",
heading: "Filtered entities card",
config: `
- type: entity-filter
entities:
@@ -74,7 +74,27 @@ const CONFIGS = [
`,
},
{
heading: "With card config",
heading: 'With "entities" card config',
config: `
- type: entity-filter
entities:
- device_tracker.demo_anne_therese
- device_tracker.demo_home_boy
- device_tracker.demo_paulus
- light.bed_light
- light.ceiling_lights
- light.kitchen_lights
state_filter:
- "on"
- not_home
card:
type: entities
title: Custom Title
show_header_toggle: false
`,
},
{
heading: 'With "glance" card config',
config: `
- type: entity-filter
entities:
@@ -89,7 +109,8 @@ const CONFIGS = [
- not_home
card:
type: glance
show_state: false
show_state: true
title: Custom Title
`,
},
];
+1 -1
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
+35 -43
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -82,7 +82,8 @@ const CONFIGS = [
heading: "With title",
config: `
- type: glance
title: This is glance
title: Custom title
columns: 4
entities:
- device_tracker.demo_paulus
- media_player.living_room
@@ -109,9 +110,10 @@ const CONFIGS = [
`,
},
{
heading: "No name",
heading: "No entity names",
config: `
- type: glance
columns: 4
show_name: false
entities:
- device_tracker.demo_paulus
@@ -124,9 +126,10 @@ const CONFIGS = [
`,
},
{
heading: "No state",
heading: "No state labels",
config: `
- type: glance
columns: 4
show_state: false
entities:
- device_tracker.demo_paulus
@@ -139,9 +142,10 @@ const CONFIGS = [
`,
},
{
heading: "No name and no state",
heading: "No names and no state labels",
config: `
- type: glance
columns: 4
show_name: false
show_state: false
entities:
@@ -155,47 +159,24 @@ const CONFIGS = [
`,
},
{
heading: "Custom name, custom icon",
heading: "Custom name + custom icon",
config: `
- type: glance
columns: 4
entities:
- entity: device_tracker.demo_paulus
name: ¯\\_(ツ)_/¯
icon: mdi:home-assistant
- media_player.living_room
- sun.sun
- cover.kitchen_window
- entity: light.kitchen_lights
icon: mdi:alarm-light
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "Custom tap action",
config: `
- type: glance
entities:
- entity: lock.kitchen_door
tap_action:
type: toggle
- entity: light.ceiling_lights
tap_action:
action: call-service
service: light.turn_on
service_data:
entity_id: light.ceiling_lights
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- entity: media_player.living_room
name: ¯\\_(ツ)_/¯
icon: mdi:home-assistant
`,
},
{
heading: "Selectively hidden name",
config: `
- type: glance
columns: 4
entities:
- device_tracker.demo_paulus
- entity: media_player.living_room
@@ -204,21 +185,32 @@ const CONFIGS = [
- entity: cover.kitchen_window
name:
- light.kitchen_lights
- entity: lock.kitchen_door
name:
- light.ceiling_lights
`,
},
{
heading: "Primary theme",
heading: "Custom tap action",
config: `
- type: glance
theming: primary
columns: 4
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
- entity: lock.kitchen_door
name: Custom
tap_action:
type: toggle
- entity: light.ceiling_lights
name: Custom
tap_action:
action: call-service
service: light.turn_on
service_data:
entity_id: light.ceiling_lights
- entity: sun.sun
name: Regular
- entity: light.kitchen_lights
name: Regular
`,
},
];
+1 -1
View File
@@ -1,4 +1,4 @@
import { html, LitElement, customElement, TemplateResult } from "lit-element";
import { customElement, html, LitElement, TemplateResult } from "lit-element";
import "../components/demo-cards";
const CONFIGS = [
+1 -1
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
+1 -1
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
+1 -1
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -31,9 +31,9 @@ const CONFIGS = [
- entity: media_player.android_cast
name: Screen casting
- entity: media_player.image_display
name: Digital Picture Frame
name: Digital Picture Frame
- entity: media_player.sonos_idle
name: Sonos Idle
name: Sonos Idle
- entity: media_player.idle_browse_media
name: Idle waiting for Browse Media
- entity: media_player.theater_off
@@ -43,7 +43,7 @@ const CONFIGS = [
- entity: media_player.theater_off_static
name: Player Off (cannot be switched on)
- entity: media_player.theater_on_static
name: Player On (cannot be switched off)
name: Player On (cannot be switched off)
- entity: media_player.idle
name: Player Idle
- entity: media_player.playing
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
+1 -1
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
+1 -1
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
PropertyValues,
query,
TemplateResult,
+4 -4
View File
@@ -1,7 +1,7 @@
import {
customElement,
html,
LitElement,
customElement,
property,
PropertyValues,
query,
@@ -10,20 +10,20 @@ import {
import "../../../src/components/ha-card";
import {
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT,
SUPPORT_FLASH,
SUPPORT_COLOR,
SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
} from "../../../src/data/light";
import "../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../src/fake_data/entity";
import {
provideHass,
MockHomeAssistant,
provideHass,
} from "../../../src/fake_data/provide_hass";
import "../components/demo-more-infos";
import "../../../src/dialogs/more-info/more-info-content";
const ENTITIES = [
getEntity("light", "bed_light", "on", {
+35 -38
View File
@@ -11,17 +11,18 @@ import {
PropertyValues,
} from "lit-element";
import { html, TemplateResult } from "lit-html";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/common/search/search-input";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-svg-icon";
import {
fetchHassioAddonsInfo,
HassioAddonInfo,
HassioAddonRepository,
reloadHassioAddons,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../../src/types";
@@ -49,46 +50,27 @@ const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
class HassioAddonStore extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) private _addons?: HassioAddonInfo[];
@property({ attribute: false }) private _repos?: HassioAddonRepository[];
@internalProperty() private _filter?: string;
public async refreshData() {
this._repos = undefined;
this._addons = undefined;
this._filter = undefined;
await reloadHassioAddons(this.hass);
await this._loadData();
}
protected render(): TemplateResult {
const repos: TemplateResult[] = [];
let repos: TemplateResult[] = [];
if (this._repos) {
for (const repo of this._repos) {
const addons = this._addons!.filter(
(addon) => addon.repository === repo.slug
);
if (addons.length === 0) {
continue;
}
repos.push(html`
<hassio-addon-repository
.hass=${this.hass}
.repo=${repo}
.addons=${addons}
.filter=${this._filter!}
></hassio-addon-repository>
`);
}
if (this.supervisor.addon.repositories) {
repos = this.addonRepositories(
this.supervisor.addon.repositories,
this.supervisor.addon.addons
);
}
return html`
@@ -157,6 +139,27 @@ class HassioAddonStore extends LitElement {
this._loadData();
}
private addonRepositories = memoizeOne(
(repositories: HassioAddonRepository[], addons: HassioAddonInfo[]) => {
return repositories.sort(sortRepos).map((repo) => {
const filteredAddons = addons.filter(
(addon) => addon.repository === repo.slug
);
return filteredAddons.length !== 0
? html`
<hassio-addon-repository
.hass=${this.hass}
.repo=${repo}
.addons=${filteredAddons}
.filter=${this._filter!}
></hassio-addon-repository>
`
: html``;
});
}
);
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
@@ -179,7 +182,7 @@ class HassioAddonStore extends LitElement {
private async _manageRepositories() {
showRepositoriesDialog(this, {
repos: this._repos!,
repos: this.supervisor.addon.repositories,
loadData: () => this._loadData(),
});
}
@@ -189,14 +192,8 @@ class HassioAddonStore extends LitElement {
}
private async _loadData() {
try {
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
this._repos = addonsInfo.repositories;
this._repos.sort(sortRepos);
this._addons = addonsInfo.addons;
} catch (err) {
alert(extractApiErrorMessage(err));
}
fireEvent(this, "supervisor-store-refresh", { store: "addon" });
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
}
private async _filterChanged(e) {
@@ -7,13 +7,14 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card";
import {
HassioAddonDetails,
@@ -28,7 +29,6 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
import "../../../../src/components/buttons/ha-progress-button";
@customElement("hassio-addon-audio")
class HassioAddonAudio extends LitElement {
@@ -7,11 +7,11 @@ import {
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import "../../../../src/components/ha-circular-progress";
import "./hassio-addon-audio";
import "./hassio-addon-config";
import "./hassio-addon-network";
@@ -26,28 +26,41 @@ class HassioAddonConfigDashboard extends LitElement {
if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`;
}
const hasOptions =
this.addon.options && Object.keys(this.addon.options).length;
const hasSchema =
hasOptions && this.addon.schema && Object.keys(this.addon.schema).length;
return html`
<div class="content">
<hassio-addon-config
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-config>
${this.addon.network
${hasOptions || hasSchema || this.addon.network || this.addon.audio
? html`
<hassio-addon-network
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-network>
${hasOptions || hasSchema
? html`
<hassio-addon-config
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-config>
`
: ""}
${this.addon.network
? html`
<hassio-addon-network
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-network>
`
: ""}
${this.addon.audio
? html`
<hassio-addon-audio
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-audio>
`
: ""}
`
: ""}
${this.addon.audio
? html`
<hassio-addon-audio
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-audio>
`
: ""}
: "This add-on does not expose configuration for you to mess with.... 👋"}
</div>
`;
}
@@ -1,4 +1,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 {
css,
@@ -14,7 +17,9 @@ import {
} from "lit-element";
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-form/ha-form";
import "../../../../src/components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
import {
@@ -29,35 +34,67 @@ import type { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
@customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string;
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) private _configHasChanged = false;
@property({ type: Boolean }) private _valid = true;
@query("ha-yaml-editor", true) private _editor!: HaYamlEditor;
@internalProperty() private _canShowSchema = false;
@internalProperty() private _error?: string;
@internalProperty() private _options?: Record<string, unknown>;
@internalProperty() private _yamlMode = false;
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
protected render(): TemplateResult {
return html`
<h1>${this.addon.name}</h1>
<ha-card header="Configuration">
<div class="card-content">
<ha-yaml-editor
@value-changed=${this._configChanged}
></ha-yaml-editor>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${this._valid ? "" : html` <div class="errors">Invalid YAML</div> `}
<ha-card>
<div class="header">
<h2>Configuration</h2>
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item .disabled=${!this._canShowSchema}>
${this._yamlMode ? "Edit in UI" : "Edit in YAML"}
</mwc-list-item>
<mwc-list-item class="warning">
Reset to defaults
</mwc-list-item>
</ha-button-menu>
</div>
</div>
<div class="card-actions">
<ha-progress-button class="warning" @click=${this._resetTapped}>
Reset to defaults
</ha-progress-button>
<div class="card-content">
${!this._yamlMode && this._canShowSchema && this.addon.schema
? html`<ha-form
.data=${this._options!}
@value-changed=${this._configChanged}
.schema=${this.addon.schema}
></ha-form>`
: html` <ha-yaml-editor
@value-changed=${this._configChanged}
></ha-yaml-editor>`}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${!this._yamlMode ||
(this._canShowSchema && this.addon.schema) ||
this._valid
? ""
: html` <div class="errors">Invalid YAML</div> `}
</div>
<div class="card-actions right">
<ha-progress-button
@click=${this._saveTapped}
.disabled=${!this._configHasChanged || !this._valid}
@@ -69,16 +106,55 @@ class HassioAddonConfig extends LitElement {
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._canShowSchema =
Object.keys(this.addon.options).length !== 0 &&
!this.addon.schema!.find(
// @ts-ignore
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
);
this._yamlMode = !this._canShowSchema;
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has("addon")) {
this._editor.setValue(this.addon.options);
this._options = { ...this.addon.options };
}
super.updated(changedProperties);
if (
changedProperties.has("_yamlMode") ||
changedProperties.has("_options")
) {
if (this._yamlMode) {
const editor = this._editor;
if (editor) {
editor.setValue(this._options!);
}
}
}
}
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._yamlMode = !this._yamlMode;
break;
case 1:
this._resetTapped(ev);
break;
}
}
private _configChanged(ev): void {
this._configHasChanged = true;
this._valid = ev.detail.isValid;
if (this.addon.schema && this._canShowSchema && !this._yamlMode) {
this._valid = true;
this._configHasChanged = true;
this._options! = ev.detail.value;
} else {
this._configHasChanged = true;
this._valid = ev.detail.isValid;
}
}
private async _resetTapped(ev: CustomEvent): Promise<void> {
@@ -122,18 +198,13 @@ class HassioAddonConfig extends LitElement {
const button = ev.currentTarget as any;
button.progress = true;
let data: HassioAddonSetOptionParams;
this._error = undefined;
try {
data = {
options: this._editor.value,
};
} catch (err) {
this._error = err;
return;
}
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
await setHassioAddonOption(this.hass, this.addon.slug, {
options: this._yamlMode ? this._editor?.value : this._options,
});
this._configHasChanged = false;
const eventdata = {
success: true,
@@ -178,6 +249,32 @@ class HassioAddonConfig extends LitElement {
.syntaxerror {
color: var(--error-color);
}
.card-menu {
float: right;
z-index: 3;
--mdc-theme-text-primary-on-background: var(--primary-text-color);
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
.header {
display: flex;
justify-content: space-between;
}
.header h2 {
color: var(--ha-card-header-color, --primary-text-color);
font-family: var(--ha-card-header-font-family, inherit);
font-size: var(--ha-card-header-font-size, 24px);
letter-spacing: -0.012em;
line-height: 48px;
padding: 12px 16px 16px;
display: block;
margin-block: 0px;
font-weight: normal;
}
.card-actions.right {
justify-content: flex-end;
}
`,
];
}
+50 -10
View File
@@ -9,17 +9,25 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-circular-progress";
import {
fetchHassioAddonInfo,
HassioAddonDetails,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-error-screen";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-tabs-subpage";
import "../../../src/components/ha-circular-progress";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
@@ -35,12 +43,16 @@ import "./log/hassio-addon-logs";
class HassioAddonDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public addon?: HassioAddonDetails;
@property({ type: Boolean }) public narrow!: boolean;
@internalProperty() _error?: string;
private _computeTail = memoizeOne((route: Route) => {
const dividerPos = route.path.indexOf("/", 1);
return dividerPos === -1
@@ -55,8 +67,14 @@ class HassioAddonDashboard extends LitElement {
});
protected render(): TemplateResult {
if (this._error) {
return html`<hass-error-screen
.error=${this._error}
></hass-error-screen>`;
}
if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`;
return html`<hass-loading-screen></hass-loading-screen>`;
}
const addonTabs: PageNavigation[] = [
@@ -106,6 +124,7 @@ class HassioAddonDashboard extends LitElement {
.route=${route}
.narrow=${this.narrow}
.hass=${this.hass}
.supervisor=${this.supervisor}
.addon=${this.addon}
></hassio-addon-router>
</hass-tabs-subpage>
@@ -152,30 +171,51 @@ class HassioAddonDashboard extends LitElement {
}
protected async firstUpdated(): Promise<void> {
await this._routeDataChanged(this.route);
if (this.route.path === "") {
const addon = extractSearchParam("addon");
if (addon) {
navigate(this, `/hassio/addon/${addon}`, true);
}
}
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
}
private async _apiCalled(ev): Promise<void> {
const path: string = ev.detail.path;
const pathSplit: string[] = ev.detail.path?.split("/");
if (!path) {
if (!pathSplit || pathSplit.length === 0) {
return;
}
const path: string = pathSplit[pathSplit.length - 1];
if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
}
if (path === "uninstall") {
history.back();
window.history.back();
} else {
await this._routeDataChanged(this.route);
await this._routeDataChanged();
}
}
private async _routeDataChanged(routeData: Route): Promise<void> {
const addon = routeData.path.split("/")[1];
protected updated(changedProperties) {
if (changedProperties.has("route") && !this.addon) {
this._routeDataChanged();
}
}
private async _routeDataChanged(): Promise<void> {
const addon = this.route.path.split("/")[1];
if (!addon) {
return;
}
try {
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo;
} catch {
} catch (err) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined;
}
}
@@ -1,5 +1,6 @@
import { customElement, property } from "lit-element";
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
HassRouterPage,
RouterOptions,
@@ -17,6 +18,8 @@ class HassioAddonRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon!: HassioAddonDetails;
protected routerOptions: RouterOptions = {
@@ -41,6 +44,7 @@ class HassioAddonRouter extends HassRouterPage {
protected updatePageEl(el) {
el.route = this.routeTail;
el.hass = this.hass;
el.supervisor = this.supervisor;
el.addon = this.addon;
el.narrow = this.narrow;
}
@@ -7,8 +7,9 @@ import {
property,
TemplateResult,
} from "lit-element";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
@@ -20,6 +21,8 @@ class HassioAddonInfoDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon?: HassioAddonDetails;
protected render(): TemplateResult {
@@ -32,6 +35,7 @@ class HassioAddonInfoDashboard extends LitElement {
<hassio-addon-info
.narrow=${this.narrow}
.hass=${this.hass}
.supervisor=${this.supervisor}
.addon=${this.addon}
></hassio-addon-info>
</div>
+528 -333
View File
@@ -43,22 +43,33 @@ import {
HassioAddonSetOptionParams,
HassioAddonSetSecurityParams,
installHassioAddon,
restartHassioAddon,
setHassioAddonOption,
setHassioAddonSecurity,
startHassioAddon,
stopHassioAddon,
uninstallHassioAddon,
updateHassioAddon,
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
extractApiErrorMessage,
fetchHassioStats,
HassioStats,
} from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { bytesToString } from "../../../../src/util/bytes-to-string";
import "../../components/hassio-card-content";
import "../../components/supervisor-metric";
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
import { hassioStyle } from "../../resources/hassio-style";
import { addonArchIsSupported } from "../../util/addon";
const STAGE_ICON = {
stable: mdiCheckCircle,
@@ -131,9 +142,26 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _metrics?: HassioStats;
@internalProperty() private _error?: string;
protected render(): TemplateResult {
const metrics = [
{
description: "Add-on CPU Usage",
value: this._metrics?.cpu_percent,
},
{
description: "Add-on RAM Usage",
value: this._metrics?.memory_percent,
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
this._metrics?.memory_limit
)}`,
},
];
return html`
${this.addon.update_available
? html`
@@ -149,21 +177,31 @@ class HassioAddonInfo extends LitElement {
iconClass="update"
></hassio-card-content>
${!this.addon.available
? html`
<p>
This update is no longer compatible with your system.
</p>
`
? !addonArchIsSupported(
this.supervisor.info.supported_arch,
this.addon.arch
)
? html`
<p>
This add-on is not compatible with the processor of
your device or the operating system you have installed
on your device.
</p>
`
: html`
<p>
You are running Home Assistant
${this.supervisor.core.version}, to update to this
version of the add-on you need at least version
${this.addon.homeassistant} of Home Assistant
</p>
`
: ""}
</div>
<div class="card-actions">
<ha-call-api-button
.hass=${this.hass}
.disabled=${!this.addon.available}
path="hassio/addons/${this.addon.slug}/update"
>
<ha-progress-button @click=${this._updateClicked}>
Update
</ha-call-api-button>
</ha-progress-button>
${this.addon.changelog
? html`
<mwc-button @click=${this._openChangelog}>
@@ -237,331 +275,378 @@ class HassioAddonInfo extends LitElement {
>
for details.
</div>
${this.addon.logo
? html`
<img
class="logo"
src="/api/hassio/addons/${this.addon.slug}/logo"
/>
`
: ""}
<div class="security">
${this.addon.stage !== "stable"
? html` <ha-label-badge
<div class="addon-container">
<div>
${this.addon.logo
? html`
<img
class="logo"
src="/api/hassio/addons/${this.addon.slug}/logo"
/>
`
: ""}
<div class="security">
${this.addon.stage !== "stable"
? html` <ha-label-badge
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
label="stage"
description=""
>
<ha-svg-icon
.path=${STAGE_ICON[this.addon.stage]}
></ha-svg-icon>
</ha-label-badge>`
: ""}
<ha-label-badge
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
green: [5, 6].includes(Number(this.addon.rating)),
yellow: [3, 4].includes(Number(this.addon.rating)),
red: [1, 2].includes(Number(this.addon.rating)),
})}
@click=${this._showMoreInfo}
id="stage"
label="stage"
id="rating"
.value=${this.addon.rating}
label="rating"
description=""
>
<ha-svg-icon
.path=${STAGE_ICON[this.addon.stage]}
></ha-svg-icon>
</ha-label-badge>`
: ""}
></ha-label-badge>
${this.addon.host_network
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_network"
label="host"
description=""
>
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.full_access
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="full_access"
label="hardware"
description=""
>
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="homeassistant_api"
label="hass"
description=""
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this._computeHassioApi
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="hassio_api"
label="hassio"
.description=${this.addon.hassio_role}
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.docker_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="docker_api"
label="docker"
description=""
>
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.host_pid
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_pid"
label="host pid"
description=""
>
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.apparmor
? html`
<ha-label-badge
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
label="apparmor"
description=""
>
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.auth_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="auth_api"
label="auth"
description=""
>
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.ingress
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="ingress"
label="ingress"
description=""
>
<ha-svg-icon
.path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
</ha-label-badge>
`
: ""}
</div>
<ha-label-badge
class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)),
yellow: [3, 4].includes(Number(this.addon.rating)),
red: [1, 2].includes(Number(this.addon.rating)),
})}
@click=${this._showMoreInfo}
id="rating"
.value=${this.addon.rating}
label="rating"
description=""
></ha-label-badge>
${this.addon.host_network
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_network"
label="host"
description=""
>
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.full_access
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="full_access"
label="hardware"
description=""
>
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="homeassistant_api"
label="hass"
description=""
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this._computeHassioApi
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="hassio_api"
label="hassio"
.description=${this.addon.hassio_role}
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.docker_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="docker_api"
label="docker"
description=""
>
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.host_pid
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_pid"
label="host pid"
description=""
>
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.apparmor
? html`
<ha-label-badge
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
label="apparmor"
description=""
>
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.auth_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="auth_api"
label="auth"
description=""
>
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.ingress
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="ingress"
label="ingress"
description=""
>
<ha-svg-icon
.path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.version
? html`
<div
class="${classMap({
"addon-options": true,
started: this.addon.state === "started",
})}"
>
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Start on boot
</span>
<span slot="description">
Make the add-on start during a system boot
</span>
<ha-switch
@change=${this._startOnBootToggled}
.checked=${this.addon.boot === "auto"}
haptic
></ha-switch>
</ha-settings-row>
${this.addon.startup !== "once"
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Watchdog
</span>
<span slot="description">
This will start the add-on if it crashes
</span>
<ha-switch
@change=${this._watchdogToggled}
.checked=${this.addon.watchdog}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this.addon.auto_update ||
this.hass.userData?.showAdvanced
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Auto update
</span>
<span slot="description">
Auto update the add-on when there is a new
version available
</span>
<ha-switch
@change=${this._autoUpdateToggled}
.checked=${this.addon.auto_update}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this.addon.ingress
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Show in sidebar
</span>
<span slot="description">
${this._computeCannotIngressSidebar
? "This option requires Home Assistant 0.92 or later."
: "Add this add-on to your sidebar"}
</span>
<ha-switch
@change=${this._panelToggled}
.checked=${this.addon.ingress_panel}
.disabled=${this._computeCannotIngressSidebar}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this._computeUsesProtectedOptions
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Protection mode
</span>
<span slot="description">
Blocks elevated system access from the add-on
</span>
<ha-switch
@change=${this._protectionToggled}
.checked=${this.addon.protected}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
</div>
`
: ""}
</div>
<div>
${this.addon.state === "started"
? html`<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Hostname
</span>
<code slot="description">
${this.addon.hostname}
</code>
</ha-settings-row>
${metrics.map(
(metric) =>
html`
<supervisor-metric
.description=${metric.description}
.value=${metric.value ?? 0}
.tooltip=${metric.tooltip}
></supervisor-metric>
`
)}`
: ""}
</div>
</div>
${this.addon.version
? html`
<div class="addon-options">
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Start on boot
</span>
<span slot="description">
Make the add-on start during a system boot
</span>
<ha-switch
@change=${this._startOnBootToggled}
.checked=${this.addon.boot === "auto"}
haptic
></ha-switch>
</ha-settings-row>
${this.addon.startup !== "once"
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Watchdog
</span>
<span slot="description">
This will start the add-on if it crashes
</span>
<ha-switch
@change=${this._watchdogToggled}
.checked=${this.addon.watchdog}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this.addon.auto_update || this.hass.userData?.showAdvanced
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Auto update
</span>
<span slot="description">
Auto update the add-on when there is a new version
available
</span>
<ha-switch
@change=${this._autoUpdateToggled}
.checked=${this.addon.auto_update}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this.addon.ingress
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Show in sidebar
</span>
<span slot="description">
${this._computeCannotIngressSidebar
? "This option requires Home Assistant 0.92 or later."
: "Add this add-on to your sidebar"}
</span>
<ha-switch
@change=${this._panelToggled}
.checked=${this.addon.ingress_panel}
.disabled=${this._computeCannotIngressSidebar}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this._computeUsesProtectedOptions
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Protection mode
</span>
<span slot="description">
Blocks elevated system access from the add-on
</span>
<ha-switch
@change=${this._protectionToggled}
.checked=${this.addon.protected}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
</div>
`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${!this.addon.available
? !addonArchIsSupported(
this.supervisor.info.supported_arch,
this.addon.arch
)
? html`
<p class="warning">
This add-on is not compatible with the processor of your
device or the operating system you have installed on your
device.
</p>
`
: html`
<p class="warning">
You are running Home Assistant
${this.supervisor.core.version}, to install this add-on you
need at least version ${this.addon.homeassistant} of Home
Assistant
</p>
`
: ""}
</div>
<div class="card-actions">
${this.addon.version
? html`
${this._computeIsRunning
? html`
<ha-call-api-button
class="warning"
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/stop"
>
Stop
</ha-call-api-button>
<ha-call-api-button
class="warning"
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/restart"
>
Restart
</ha-call-api-button>
`
: html`
<ha-progress-button @click=${this._startClicked}>
Start
</ha-progress-button>
`}
${this._computeShowWebUI
? html`
<a
href=${this._pathWebui!}
tabindex="-1"
target="_blank"
class="right"
rel="noopener"
>
<mwc-button>
<div>
${this.addon.version
? this._computeIsRunning
? html`
<ha-progress-button
class="warning"
@click=${this._stopClicked}
>
Stop
</ha-progress-button>
<ha-progress-button
class="warning"
@click=${this._restartClicked}
>
Restart
</ha-progress-button>
`
: html`
<ha-progress-button @click=${this._startClicked}>
Start
</ha-progress-button>
`
: html`
<ha-progress-button
.disabled=${!this.addon.available}
@click=${this._installClicked}
>
Install
</ha-progress-button>
`}
</div>
<div>
${this.addon.version
? html` ${this._computeShowWebUI
? html`
<a
href=${this._pathWebui!}
tabindex="-1"
target="_blank"
rel="noopener"
>
<mwc-button>
Open web UI
</mwc-button>
</a>
`
: ""}
${this._computeShowIngressUI
? html`
<mwc-button @click=${this._openIngress}>
Open web UI
</mwc-button>
</a>
`
: ""}
${this._computeShowIngressUI
? html`
<mwc-button class="right" @click=${this._openIngress}>
Open web UI
</mwc-button>
`
: ""}
<ha-progress-button
class=" right warning"
@click=${this._uninstallClicked}
>
Uninstall
</ha-progress-button>
${this.addon.build
? html`
<ha-call-api-button
class="warning right"
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/rebuild"
>
Rebuild
</ha-call-api-button>
`
: ""}
`
: html`
${!this.addon.available
? html`
<p class="warning">
This add-on is not available on your system.
</p>
`
: ""}
<ha-progress-button
.disabled=${!this.addon.available}
@click=${this._installClicked}
>
Install
</ha-progress-button>
`}
`
: ""}
<ha-progress-button
class="warning"
@click=${this._uninstallClicked}
>
Uninstall
</ha-progress-button>
${this.addon.build
? html`
<ha-call-api-button
class="warning"
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/rebuild"
>
Rebuild
</ha-call-api-button>
`
: ""}`
: ""}
</div>
</div>
</ha-card>
@@ -579,6 +664,22 @@ class HassioAddonInfo extends LitElement {
`;
}
protected updated(changedProps) {
super.updated(changedProps);
if (changedProps.has("addon")) {
this._loadData();
}
}
private async _loadData(): Promise<void> {
if (this.addon.state === "started") {
this._metrics = await fetchHassioStats(
this.hass,
`addons/${this.addon.slug}`
);
}
}
private get _computeHassioApi(): boolean {
return (
this.addon.hassio_api &&
@@ -779,6 +880,82 @@ class HassioAddonInfo extends LitElement {
button.progress = false;
}
private async _stopClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
try {
await stopHassioAddon(this.hass, this.addon.slug);
const eventdata = {
success: true,
response: undefined,
path: "stop",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: "Failed to stop addon",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _restartClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
try {
await restartHassioAddon(this.hass, this.addon.slug);
const eventdata = {
success: true,
response: undefined,
path: "stop",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: "Failed to restart addon",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _updateClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: this.addon.name,
text: "Are you sure you want to update this add-on?",
confirmText: "update add-on",
dismissText: "no",
});
if (!confirmed) {
button.progress = false;
return;
}
this._error = undefined;
try {
await updateHassioAddon(this.hass, this.addon.slug);
const eventdata = {
success: true,
response: undefined,
path: "update",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: "Failed to update addon",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _startClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
@@ -787,10 +964,10 @@ class HassioAddonInfo extends LitElement {
this.hass,
this.addon.slug
);
if (!validate.data.valid) {
if (!validate.valid) {
await showConfirmationDialog(this, {
title: "Failed to start addon - configuration validation failed!",
text: validate.data.message.split(" Got ")[0],
text: validate.message.split(" Got ")[0],
confirm: () => this._openConfiguration(),
confirmText: "Go to configuration",
dismissText: "Cancel",
@@ -810,6 +987,12 @@ class HassioAddonInfo extends LitElement {
try {
await startHassioAddon(this.hass, this.addon.slug);
this.addon = await fetchHassioAddonInfo(this.hass, this.addon.slug);
const eventdata = {
success: true,
response: undefined,
path: "start",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
showAlertDialog(this, {
title: "Failed to start addon",
@@ -925,9 +1108,6 @@ class HassioAddonInfo extends LitElement {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
protection-enable mwc-button {
--mdc-theme-primary: white;
}
@@ -950,7 +1130,8 @@ class HassioAddonInfo extends LitElement {
margin-bottom: 16px;
}
.card-actions {
display: flow-root;
justify-content: space-between;
display: flex;
}
.security h3 {
margin-bottom: 8px;
@@ -986,12 +1167,26 @@ class HassioAddonInfo extends LitElement {
}
.addon-options {
max-width: 50%;
max-width: 90%;
}
.addon-container {
display: grid;
grid-auto-flow: column;
grid-template-columns: 60% 40%;
}
.addon-container > div:last-of-type {
align-self: end;
}
@media (max-width: 720px) {
.addon-options {
max-width: 100%;
}
.addon-container {
display: block;
}
}
`,
];
@@ -7,8 +7,8 @@ import {
property,
TemplateResult,
} from "lit-element";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import "../../../../src/components/ha-circular-progress";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
@@ -0,0 +1,87 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../src/components/ha-bar";
import "../../../src/components/ha-settings-row";
import { roundWithOneDecimal } from "../../../src/util/calculate";
@customElement("supervisor-metric")
class SupervisorMetric extends LitElement {
@property({ type: Number }) public value!: number;
@property({ type: String }) public description!: string;
@property({ type: String }) public tooltip?: string;
protected render(): TemplateResult {
const roundedValue = roundWithOneDecimal(this.value);
return html`<ha-settings-row>
<span slot="heading">
${this.description}
</span>
<div slot="description" title="${this.tooltip ?? ""}">
<span class="value">
${roundedValue}%
</span>
<ha-bar
class="${classMap({
"target-warning": roundedValue > 50,
"target-critical": roundedValue > 85,
})}"
.value=${this.value}
></ha-bar>
</div>
</ha-settings-row>`;
}
static get styles(): CSSResult {
return css`
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row > div[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
display: flex;
justify-content: space-between;
}
ha-bar {
--ha-bar-primary-color: var(
--hassio-bar-ok-color,
var(--success-color)
);
}
.target-warning {
--ha-bar-primary-color: var(
--hassio-bar-warning-color,
var(--warning-color)
);
}
.target-critical {
--ha-bar-primary-color: var(
--hassio-bar-critical-color,
var(--error-color)
);
}
.value {
width: 42px;
padding-right: 4px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"supervisor-metric": SupervisorMetric;
}
}
+7
View File
@@ -10,6 +10,7 @@ import {
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon";
@@ -64,6 +65,7 @@ export class HassioUpdate extends LitElement {
<div class="card-group">
${this._renderUpdateCard(
"Home Assistant Core",
"core",
this.supervisor.core,
"hassio/homeassistant/update",
`https://${
@@ -72,6 +74,7 @@ export class HassioUpdate extends LitElement {
)}
${this._renderUpdateCard(
"Supervisor",
"supervisor",
this.supervisor.supervisor,
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
@@ -79,6 +82,7 @@ export class HassioUpdate extends LitElement {
${this.supervisor.host.features.includes("hassos")
? this._renderUpdateCard(
"Operating System",
"os",
this.supervisor.os,
"hassio/os/update",
`https://github.com//home-assistant/hassos/releases/tag/${this.supervisor.os.version_latest}`
@@ -91,6 +95,7 @@ export class HassioUpdate extends LitElement {
private _renderUpdateCard(
name: string,
key: string,
object: HassioHomeAssistantInfo | HassioSupervisorInfo | HassioHassOSInfo,
apiPath: string,
releaseNotesUrl: string
@@ -116,6 +121,7 @@ export class HassioUpdate extends LitElement {
<ha-progress-button
.apiPath=${apiPath}
.name=${name}
.key=${key}
.version=${object.version_latest}
@click=${this._confirmUpdate}
>
@@ -142,6 +148,7 @@ export class HassioUpdate extends LitElement {
}
try {
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
fireEvent(this, "supervisor-store-refresh", { store: item.key });
} catch (err) {
// Only show an error if the status code was not expected (user behind proxy)
// or no status at all(connection terminated)
@@ -3,9 +3,9 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
@@ -22,7 +22,11 @@ import {
fetchHassioSnapshotInfo,
HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
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";
@@ -75,6 +79,8 @@ interface FolderItem {
class HassioSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor?: Supervisor;
@internalProperty() private _error?: string;
@internalProperty() private _onboarding = false;
@@ -102,6 +108,7 @@ class HassioSnapshotDialog extends LitElement {
this._dialogParams = params;
this._onboarding = params.onboarding ?? false;
this.supervisor = params.supervisor;
}
protected render(): TemplateResult {
@@ -298,6 +305,16 @@ class HassioSnapshotDialog extends LitElement {
}
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?",
@@ -359,6 +376,16 @@ class HassioSnapshotDialog extends LitElement {
}
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:
@@ -1,9 +1,11 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioSnapshotDialogParams {
slug: string;
onDelete?: () => void;
onboarding?: boolean;
supervisor?: Supervisor;
}
export const showHassioSnapshotDialog = (
+2 -1
View File
@@ -1,6 +1,7 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import "../../src/resources/safari-14-attachshadow-patch";
import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch";
import "./hassio-main";
const styleEl = document.createElement("style");
+15 -6
View File
@@ -1,11 +1,13 @@
import { html, PropertyValues, customElement, property } from "lit-element";
import "./hassio-router";
import { HomeAssistant, Route } from "../../src/types";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { customElement, html, property, PropertyValues } from "lit-element";
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 { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { supervisorStore } from "../../src/data/supervisor/supervisor";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { atLeastVersion } from "../../src/common/config/version";
import "../../src/layouts/hass-loading-screen";
import { HomeAssistant, Route } from "../../src/types";
import "./hassio-router";
import { SupervisorBaseElement } from "./supervisor-base-element";
@customElement("hassio-main")
@@ -71,8 +73,15 @@ export class HassioMain extends SupervisorBaseElement {
protected render() {
if (!this.supervisor || !this.hass) {
return html``;
return html`<hass-loading-screen></hass-loading-screen>`;
}
if (
Object.keys(supervisorStore).some((store) => !this.supervisor![store])
) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
return html`
<hassio-router
.hass=${this.hass}
+128
View File
@@ -0,0 +1,128 @@
import {
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { sanitizeUrl } from "@braintree/sanitize-url";
import {
createSearchParam,
extractSearchParamsObject,
} from "../../src/common/url/search-params";
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";
const REDIRECTS: Redirects = {
supervisor_logs: {
redirect: "/hassio/system",
},
supervisor_info: {
redirect: "/hassio/system",
},
supervisor_snapshots: {
redirect: "/hassio/snapshots",
},
supervisor_store: {
redirect: "/hassio/store",
},
supervisor: {
redirect: "/hassio/dashboard",
},
supervisor_addon: {
redirect: "/hassio/addon",
params: {
addon: "string",
},
},
};
@customElement("hassio-my-redirect")
class HassioMyRedirect extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public route!: Route;
@internalProperty() public _error?: TemplateResult | string;
connectedCallback() {
super.connectedCallback();
const path = this.route.path.substr(1);
const redirect = REDIRECTS[path];
if (!redirect) {
this._error = html`This redirect is not supported by your Home Assistant
instance. Check the
<a
target="_blank"
rel="noreferrer noopener"
href="https://my.home-assistant.io/faq.html#supported-pages"
>My Home Assistant FAQ</a
>
for the supported redirects and the version they where introduced.`;
return;
}
let url: string;
try {
url = this._createRedirectUrl(redirect);
} catch (err) {
this._error = "An unknown error occured";
return;
}
navigate(this, url, true);
}
protected render(): TemplateResult {
if (this._error) {
return html`<hass-error-screen
.error=${this._error}
></hass-error-screen>`;
}
return html``;
}
private _createRedirectUrl(redirect: Redirect): string {
const params = this._createRedirectParams(redirect);
return `${redirect.redirect}${params}`;
}
private _createRedirectParams(redirect: Redirect): string {
const params = extractSearchParamsObject();
if (!redirect.params && !Object.keys(params).length) {
return "";
}
const resultParams = {};
Object.entries(redirect.params || {}).forEach(([key, type]) => {
if (!params[key] || !this._checkParamType(type, params[key])) {
throw Error();
}
resultParams[key] = params[key];
});
return `?${createSearchParam(resultParams)}`;
}
private _checkParamType(type: ParamType, value: string) {
if (type === "string") {
return true;
}
if (type === "url") {
return value && value === sanitizeUrl(value);
}
return false;
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-my-redirect": HassioMyRedirect;
}
}
+6 -1
View File
@@ -41,6 +41,10 @@ class HassioRouter extends HassRouterPage {
tag: "hassio-ingress-view",
load: () => import("./ingress-view/hassio-ingress-view"),
},
_my_redirect: {
tag: "hassio-my-redirect",
load: () => import("./hassio-my-redirect"),
},
},
};
@@ -49,12 +53,13 @@ class HassioRouter extends HassRouterPage {
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
el.hass = this.hass;
el.supervisor = this.supervisor;
el.narrow = this.narrow;
el.route = route;
if (el.localName === "hassio-ingress-view") {
el.ingressPanel = this.panel.config && this.panel.config.ingress;
} else {
el.supervisor = this.supervisor;
}
}
@@ -1,14 +1,17 @@
import { mdiMenu } from "@mdi/js";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate";
import {
fetchHassioAddonInfo,
HassioAddonDetails,
@@ -17,13 +20,10 @@ import {
createHassioSession,
validateHassioSession,
} from "../../../src/data/hassio/ingress";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage";
import { HomeAssistant, Route } from "../../../src/types";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { navigate } from "../../../src/common/navigate";
import { mdiMenu } from "@mdi/js";
import { fireEvent } from "../../../src/common/dom/fire_event";
@customElement("hassio-ingress-view")
class HassioIngressView extends LitElement {
+17 -2
View File
@@ -41,6 +41,7 @@ import {
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";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { haStyle } from "../../../src/resources/styles";
@@ -211,7 +212,13 @@ class HassioSnapshots extends LitElement {
: undefined}
</div>
<div class="card-actions">
<ha-progress-button @click=${this._createSnapshot}>
<ha-progress-button
@click=${this._createSnapshot}
title="${this.supervisor.info.state !== "running"
? `Creating a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`
: ""}"
.disabled=${this.supervisor.info.state !== "running"}
>
Create
</ha-progress-button>
</div>
@@ -264,7 +271,7 @@ class HassioSnapshots extends LitElement {
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("supervisorInfo")) {
if (changedProps.has("supervisor")) {
this._addonList = this.supervisor.supervisor.addons
.map((addon) => ({
slug: addon.slug,
@@ -325,6 +332,12 @@ class HassioSnapshots extends LitElement {
}
private async _createSnapshot(ev: CustomEvent): Promise<void> {
if (this.supervisor.info.state !== "running") {
await showAlertDialog(this, {
title: "Could not create snapshot",
text: `Creating a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
});
}
const button = ev.currentTarget as any;
button.progress = true;
@@ -386,6 +399,7 @@ class HassioSnapshots extends LitElement {
private _snapshotClicked(ev) {
showHassioSnapshotDialog(this, {
slug: ev.currentTarget!.snapshot.slug,
supervisor: this.supervisor,
onDelete: () => this._updateSnapshots(),
});
}
@@ -395,6 +409,7 @@ class HassioSnapshots extends LitElement {
showSnapshot: (slug: string) =>
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this._updateSnapshots(),
}),
reloadSnapshot: () => this.refreshData(),
+135 -5
View File
@@ -1,4 +1,16 @@
import { LitElement, property, PropertyValues } from "lit-element";
import { Collection, UnsubscribeFunc } from "home-assistant-js-websocket";
import {
internalProperty,
LitElement,
property,
PropertyValues,
} from "lit-element";
import { atLeastVersion } from "../../src/common/config/version";
import { fetchHassioAddonsInfo } from "../../src/data/hassio/addon";
import {
hassioApiResultExtractor,
HassioResponse,
} from "../../src/data/hassio/common";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
@@ -10,13 +22,23 @@ import {
fetchHassioInfo,
fetchHassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import {
getSupervisorEventCollection,
subscribeSupervisorEvents,
Supervisor,
supervisorApiRequest,
SupervisorAPIRequestParams,
supervisorApiWsRequest,
SupervisorObject,
supervisorStore,
} from "../../src/data/supervisor/supervisor";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
declare global {
interface HASSDomEvents {
"supervisor-update": Partial<Supervisor>;
"supervisor-store-refresh": { store: SupervisorObject };
}
}
@@ -25,20 +47,84 @@ export class SupervisorBaseElement extends urlSyncMixin(
) {
@property({ attribute: false }) public supervisor?: Supervisor;
@internalProperty() private _unsubs: Record<string, UnsubscribeFunc> = {};
@internalProperty() private _collections: Record<
string,
Collection<unknown>
> = {};
public disconnectedCallback() {
super.disconnectedCallback();
Object.keys(this._unsubs).forEach((unsub) => {
this._unsubs[unsub]();
});
}
protected _updateSupervisor(obj: Partial<Supervisor>): void {
this.supervisor = { ...this.supervisor!, ...obj };
this.supervisor = {
...this.supervisor!,
...obj,
callApi: (params) => this._callAPI(params),
};
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this._initSupervisor();
this.addEventListener("supervisor-update", (ev) =>
this._updateSupervisor(ev.detail)
}
private async _handleSupervisorStoreRefreshEvent(ev) {
const store = ev.detail.store;
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
this._collections[store].refresh();
return;
}
const response = await this.hass.callApi<HassioResponse<any>>(
"GET",
`hassio${supervisorStore[store]}`
);
this._updateSupervisor({ [store]: response.data });
}
private async _initSupervisor(): Promise<void> {
this.addEventListener(
"supervisor-store-refresh",
this._handleSupervisorStoreRefreshEvent
);
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
Object.keys(supervisorStore).forEach((store) => {
this._unsubs[store] = subscribeSupervisorEvents(
this.hass,
(data) => this._updateSupervisor({ [store]: data }),
store,
supervisorStore[store]
);
if (this._collections[store]) {
this._collections[store].refresh();
} else {
this._collections[store] = getSupervisorEventCollection(
this.hass.connection,
store,
supervisorStore[store]
);
}
});
if (this.supervisor === undefined) {
Object.keys(this._collections).forEach((collection) =>
this._updateSupervisor({
[collection]: this._collections[collection].state,
})
);
}
return;
}
const [
addon,
supervisor,
host,
core,
@@ -47,6 +133,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
network,
resolution,
] = await Promise.all([
fetchHassioAddonsInfo(this.hass),
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
@@ -57,6 +144,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
]);
this.supervisor = {
addon,
supervisor,
host,
core,
@@ -64,6 +152,48 @@ export class SupervisorBaseElement extends urlSyncMixin(
os,
network,
resolution,
callApi: (params) => this._callAPI(params),
};
this.addEventListener("supervisor-update", (ev) =>
this._updateSupervisor(ev.detail)
);
}
private async _callAPI<T>(params: SupervisorAPIRequestParams): Promise<T> {
const hasHass = this.hass !== undefined;
const canUseWS =
!params.rest &&
hasHass &&
atLeastVersion(this.hass.config.version, 2021, 2, 4);
if (canUseWS) {
const connection = hasHass ? this.hass.connection : params.connection;
if (connection === undefined) {
throw Error(`No connection found, aborting API call - ${params}`);
}
const requestParams: supervisorApiRequest = {
...params,
};
delete requestParams.rest;
delete requestParams.connection;
return await supervisorApiWsRequest<T>(connection, requestParams);
} else {
const method =
params.method === "post"
? "POST"
: params.method === "put"
? "PUT"
: params.method === "delete"
? "DELETE"
: "GET";
return hassioApiResultExtractor<T>(
await this.hass.callApi<HassioResponse<T>>(
method,
`hassio${params.endpoint}`,
params.data
)
);
}
}
}
+248
View File
@@ -0,0 +1,248 @@
import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
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-settings-row";
import {
extractApiErrorMessage,
fetchHassioStats,
HassioStats,
} from "../../../src/data/hassio/common";
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
import "../components/supervisor-metric";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-core-info")
class HassioCoreInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _metrics?: HassioStats;
protected render(): TemplateResult | void {
const metrics = [
{
description: "Core CPU Usage",
value: this._metrics?.cpu_percent,
},
{
description: "Core RAM Usage",
value: this._metrics?.memory_percent,
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
this._metrics?.memory_limit
)}`,
},
];
return html`
<ha-card header="Core">
<div class="card-content">
<div>
<ha-settings-row>
<span slot="heading">
Version
</span>
<span slot="description">
core-${this.supervisor.core.version}
</span>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Newest Version
</span>
<span slot="description">
core-${this.supervisor.core.version_latest}
</span>
${this.supervisor.core.update_available
? html`
<ha-progress-button
title="Update the core"
@click=${this._coreUpdate}
>
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
</div>
<div>
${metrics.map(
(metric) =>
html`
<supervisor-metric
.description=${metric.description}
.value=${metric.value ?? 0}
.tooltip=${metric.tooltip}
></supervisor-metric>
`
)}
</div>
</div>
<div class="card-actions">
<ha-progress-button
slot="primaryAction"
class="warning"
@click=${this._coreRestart}
title="Restart Home Assistant Core"
>
Restart Core
</ha-progress-button>
</div>
</ha-card>
`;
}
protected firstUpdated(): void {
this._loadData();
}
private async _loadData(): Promise<void> {
this._metrics = await fetchHassioStats(this.hass, "core");
}
private async _coreRestart(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Restart Home Assistant Core",
text: "Are you sure you want to restart Home Assistant Core",
confirmText: "restart",
dismissText: "cancel",
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await restartCore(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to restart Home Assistant Core",
text: extractApiErrorMessage(err),
});
} finally {
button.progress = false;
}
}
private async _coreUpdate(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Update Home Assistant Core",
text: `Are you sure you want to update Home Assistant Core to version ${this.supervisor.core.version_latest}?`,
confirmText: "update",
dismissText: "cancel",
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await updateCore(this.hass);
fireEvent(this, "supervisor-store-refresh", { store: "core" });
} catch (err) {
showAlertDialog(this, {
title: "Failed to update Home Assistant Core",
text: extractApiErrorMessage(err),
});
} finally {
button.progress = false;
}
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
justify-content: space-between;
flex-direction: column;
display: flex;
}
.card-actions {
height: 48px;
border-top: none;
display: flex;
justify-content: flex-end;
align-items: center;
}
.card-content {
display: flex;
flex-direction: column;
height: calc(100% - 124px);
justify-content: space-between;
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row[three-line] {
height: 74px;
}
ha-settings-row > span[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
}
.warning {
--mdc-theme-primary: var(--error-color);
}
ha-button-menu {
color: var(--secondary-text-color);
--mdc-menu-min-width: 200px;
}
@media (min-width: 563px) {
paper-listbox {
max-height: 150px;
overflow: auto;
}
}
paper-item {
cursor: pointer;
min-height: 35px;
}
mwc-list-item ha-svg-icon {
color: var(--secondary-text-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-core-info": HassioCoreInfo;
}
}
+135 -80
View File
@@ -13,6 +13,7 @@ import {
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
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-button-menu";
@@ -26,7 +27,6 @@ import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
changeHostOptions,
configSyncOS,
fetchHassioHostInfo,
rebootHost,
shutdownHost,
updateOS,
@@ -43,6 +43,11 @@ import {
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import {
getValueInPercentage,
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 { hassioStyle } from "../resources/hassio-style";
@@ -57,80 +62,117 @@ class HassioHostInfo extends LitElement {
const primaryIpAddress = this.supervisor.host.features.includes("network")
? this._primaryIpAddress(this.supervisor.network!)
: "";
return html`
<ha-card header="Host System">
<div class="card-content">
${this.supervisor.host.features.includes("hostname")
? html`<ha-settings-row>
<span slot="heading">
Hostname
</span>
<span slot="description">
${this.supervisor.host.hostname}
</span>
<mwc-button
title="Change the hostname"
label="Change"
@click=${this._changeHostnameClicked}
>
</mwc-button>
</ha-settings-row>`
: ""}
${this.supervisor.host.features.includes("network")
? html` <ha-settings-row>
<span slot="heading">
IP Address
</span>
<span slot="description">
${primaryIpAddress}
</span>
<mwc-button
title="Change the network"
label="Change"
@click=${this._changeNetworkClicked}
>
</mwc-button>
</ha-settings-row>`
: ""}
<ha-settings-row>
<span slot="heading">
Operating System
</span>
<span slot="description">
${this.supervisor.host.operating_system}
</span>
${this.supervisor.os.update_available
? html`
<ha-progress-button
title="Update the host OS"
@click=${this._osUpdate}
const metrics = [
{
description: "Used Space",
value: this._getUsedSpace(
this.supervisor.host.disk_used,
this.supervisor.host.disk_total
),
tooltip: `${this.supervisor.host.disk_used} GB/${this.supervisor.host.disk_total} GB`,
},
];
return html`
<ha-card header="Host">
<div class="card-content">
<div>
${this.supervisor.host.features.includes("hostname")
? html`<ha-settings-row>
<span slot="heading">
Hostname
</span>
<span slot="description">
${this.supervisor.host.hostname}
</span>
<mwc-button
title="Change the hostname"
label="Change"
@click=${this._changeHostnameClicked}
>
Update
</ha-progress-button>
`
</mwc-button>
</ha-settings-row>`
: ""}
</ha-settings-row>
${!this.supervisor.host.features.includes("hassos")
? html`<ha-settings-row>
<span slot="heading">
Docker version
</span>
<span slot="description">
${this.supervisor.info.docker}
</span>
</ha-settings-row>`
: ""}
${this.supervisor.host.deployment
? html`<ha-settings-row>
<span slot="heading">
Deployment
</span>
<span slot="description">
${this.supervisor.host.deployment}
</span>
</ha-settings-row>`
: ""}
${this.supervisor.host.features.includes("network")
? html` <ha-settings-row>
<span slot="heading">
IP Address
</span>
<span slot="description">
${primaryIpAddress}
</span>
<mwc-button
title="Change the network"
label="Change"
@click=${this._changeNetworkClicked}
>
</mwc-button>
</ha-settings-row>`
: ""}
<ha-settings-row>
<span slot="heading">
Operating System
</span>
<span slot="description">
${this.supervisor.host.operating_system}
</span>
${this.supervisor.os.update_available
? html`
<ha-progress-button
title="Update the host OS"
@click=${this._osUpdate}
>
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
${!this.supervisor.host.features.includes("hassos")
? html`<ha-settings-row>
<span slot="heading">
Docker version
</span>
<span slot="description">
${this.supervisor.info.docker}
</span>
</ha-settings-row>`
: ""}
${this.supervisor.host.deployment
? html`<ha-settings-row>
<span slot="heading">
Deployment
</span>
<span slot="description">
${this.supervisor.host.deployment}
</span>
</ha-settings-row>`
: ""}
</div>
<div>
${this.supervisor.host.disk_life_time !== "" &&
this.supervisor.host.disk_life_time >= 10
? html` <ha-settings-row>
<span slot="heading">
eMMC Lifetime Used
</span>
<span slot="description">
${this.supervisor.host.disk_life_time - 10}% -
${this.supervisor.host.disk_life_time}%
</span>
</ha-settings-row>`
: ""}
${metrics.map(
(metric) =>
html`
<supervisor-metric
.description=${metric.description}
.value=${metric.value ?? 0}
.tooltip=${metric.tooltip}
></supervisor-metric>
`
)}
</div>
</div>
<div class="card-actions">
${this.supervisor.host.features.includes("reboot")
@@ -140,7 +182,7 @@ class HassioHostInfo extends LitElement {
class="warning"
@click=${this._hostReboot}
>
Reboot
Reboot Host
</ha-progress-button>
`
: ""}
@@ -151,7 +193,7 @@ class HassioHostInfo extends LitElement {
class="warning"
@click=${this._hostShutdown}
>
Shutdown
Shutdown Host
</ha-progress-button>
`
: ""}
@@ -183,6 +225,10 @@ class HassioHostInfo extends LitElement {
this._loadData();
}
private _getUsedSpace = memoizeOne((used: number, total: number) =>
roundWithOneDecimal(getValueInPercentage(used, 0, total))
);
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => {
if (!network_info || !network_info.interfaces) {
return "";
@@ -294,6 +340,7 @@ class HassioHostInfo extends LitElement {
try {
await updateOS(this.hass);
fireEvent(this, "supervisor-store-refresh", { store: "os" });
} catch (err) {
showAlertDialog(this, {
title: "Failed to update",
@@ -322,8 +369,7 @@ class HassioHostInfo extends LitElement {
if (hostname && hostname !== curHostname) {
try {
await changeHostOptions(this.hass, { hostname });
const host = await fetchHassioHostInfo(this.hass);
fireEvent(this, "supervisor-update", { host });
fireEvent(this, "supervisor-store-refresh", { store: "host" });
} catch (err) {
showAlertDialog(this, {
title: "Setting hostname failed",
@@ -336,8 +382,7 @@ class HassioHostInfo extends LitElement {
private async _importFromUSB(): Promise<void> {
try {
await configSyncOS(this.hass);
const host = await fetchHassioHostInfo(this.hass);
fireEvent(this, "supervisor-update", { host });
fireEvent(this, "supervisor-store-refresh", { store: "host" });
} catch (err) {
showAlertDialog(this, {
title: "Failed to import from USB",
@@ -347,8 +392,12 @@ class HassioHostInfo extends LitElement {
}
private async _loadData(): Promise<void> {
const network = await fetchNetworkInfo(this.hass);
fireEvent(this, "supervisor-update", { network });
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
fireEvent(this, "supervisor-store-refresh", { store: "network" });
} else {
const network = await fetchNetworkInfo(this.hass);
fireEvent(this, "supervisor-update", { network });
}
}
static get styles(): CSSResult[] {
@@ -369,6 +418,12 @@ class HassioHostInfo extends LitElement {
justify-content: space-between;
align-items: center;
}
.card-content {
display: flex;
flex-direction: column;
height: calc(100% - 124px);
justify-content: space-between;
}
ha-settings-row {
padding: 0;
height: 54px;
+161 -97
View File
@@ -3,6 +3,7 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
@@ -12,9 +13,12 @@ import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-switch";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
fetchHassioSupervisorInfo,
extractApiErrorMessage,
fetchHassioStats,
HassioStats,
} from "../../../src/data/hassio/common";
import {
reloadSupervisor,
restartSupervisor,
setSupervisorOption,
@@ -28,7 +32,9 @@ import {
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
import { documentationUrl } from "../../../src/util/documentation-url";
import "../components/supervisor-metric";
import { hassioStyle } from "../resources/hassio-style";
const UNSUPPORTED_REASON = {
@@ -87,127 +93,164 @@ class HassioSupervisorInfo extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _metrics?: HassioStats;
protected render(): TemplateResult | void {
const metrics = [
{
description: "Supervisor CPU Usage",
value: this._metrics?.cpu_percent,
},
{
description: "Supervisor RAM Usage",
value: this._metrics?.memory_percent,
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
this._metrics?.memory_limit
)}`,
},
];
return html`
<ha-card header="Supervisor">
<div class="card-content">
<ha-settings-row>
<span slot="heading">
Version
</span>
<span slot="description">
${this.supervisor.supervisor.version}
</span>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Newest Version
</span>
<span slot="description">
${this.supervisor.supervisor.version_latest}
</span>
${this.supervisor.supervisor.update_available
? html`
<ha-progress-button
title="Update the supervisor"
@click=${this._supervisorUpdate}
>
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Channel
</span>
<span slot="description">
${this.supervisor.supervisor.channel}
</span>
${this.supervisor.supervisor.channel === "beta"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get stable updates for Home Assistant, supervisor and host"
>
Leave beta channel
</ha-progress-button>
`
: this.supervisor.supervisor.channel === "stable"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>
Join beta channel
</ha-progress-button>
`
: ""}
</ha-settings-row>
<div>
<ha-settings-row>
<span slot="heading">
Version
</span>
<span slot="description">
supervisor-${this.supervisor.supervisor.version}
</span>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Newest Version
</span>
<span slot="description">
supervisor-${this.supervisor.supervisor.version_latest}
</span>
${this.supervisor.supervisor.update_available
? html`
<ha-progress-button
title="Update the supervisor"
@click=${this._supervisorUpdate}
>
Update
</ha-progress-button>
`
: ""}
</ha-settings-row>
<ha-settings-row>
<span slot="heading">
Channel
</span>
<span slot="description">
${this.supervisor.supervisor.channel}
</span>
${this.supervisor.supervisor.channel === "beta"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get stable updates for Home Assistant, supervisor and host"
>
Leave beta channel
</ha-progress-button>
`
: this.supervisor.supervisor.channel === "stable"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>
Join beta channel
</ha-progress-button>
`
: ""}
</ha-settings-row>
${this.supervisor.supervisor.supported
? html` <ha-settings-row three-line>
<span slot="heading">
Share Diagnostics
</span>
<div slot="description" class="diagnostics-description">
Share crash reports and diagnostic information.
${this.supervisor.supervisor.supported
? html` <ha-settings-row three-line>
<span slot="heading">
Share Diagnostics
</span>
<div slot="description" class="diagnostics-description">
Share crash reports and diagnostic information.
<button
class="link"
title="Show more information about this"
@click=${this._diagnosticsInformationDialog}
>
Learn more
</button>
</div>
<ha-switch
haptic
.checked=${this.supervisor.supervisor.diagnostics}
@change=${this._toggleDiagnostics}
></ha-switch>
</ha-settings-row>`
: html`<div class="error">
You are running an unsupported installation.
<button
class="link"
title="Show more information about this"
@click=${this._diagnosticsInformationDialog}
title="Learn more about how you can make your system compliant"
@click=${this._unsupportedDialog}
>
Learn more
</button>
</div>
<ha-switch
haptic
.checked=${this.supervisor.supervisor.diagnostics}
@change=${this._toggleDiagnostics}
></ha-switch>
</ha-settings-row>`
: html`<div class="error">
You are running an unsupported installation.
<button
class="link"
title="Learn more about how you can make your system compliant"
@click=${this._unsupportedDialog}
>
Learn more
</button>
</div>`}
${!this.supervisor.supervisor.healthy
? html`<div class="error">
Your installation is running in an unhealthy state.
<button
class="link"
title="Learn more about why your system is marked as unhealthy"
@click=${this._unhealthyDialog}
>
Learn more
</button>
</div>`
: ""}
</div>`}
${!this.supervisor.supervisor.healthy
? html`<div class="error">
Your installation is running in an unhealthy state.
<button
class="link"
title="Learn more about why your system is marked as unhealthy"
@click=${this._unhealthyDialog}
>
Learn more
</button>
</div>`
: ""}
</div>
<div class="metrics-block">
${metrics.map(
(metric) =>
html`
<supervisor-metric
.description=${metric.description}
.value=${metric.value ?? 0}
.tooltip=${metric.tooltip}
></supervisor-metric>
`
)}
</div>
</div>
<div class="card-actions">
<ha-progress-button
@click=${this._supervisorReload}
title="Reload parts of the Supervisor"
>
Reload
Reload Supervisor
</ha-progress-button>
<ha-progress-button
class="warning"
@click=${this._supervisorRestart}
title="Restart the Supervisor"
>
Restart
Restart Supervisor
</ha-progress-button>
</div>
</ha-card>
`;
}
protected firstUpdated(): void {
this._loadData();
}
private async _loadData(): Promise<void> {
this._metrics = await fetchHassioStats(this.hass, "supervisor");
}
private async _toggleBeta(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
@@ -274,14 +317,25 @@ class HassioSupervisorInfo extends LitElement {
private async _reloadSupervisor(): Promise<void> {
await reloadSupervisor(this.hass);
const supervisor = await fetchHassioSupervisorInfo(this.hass);
fireEvent(this, "supervisor-update", { supervisor });
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
}
private async _supervisorRestart(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Restart the Supervisor",
text: "Are you sure you want to restart the Supervisor",
confirmText: "restart",
dismissText: "cancel",
});
if (!confirmed) {
button.progress = false;
return;
}
try {
await restartSupervisor(this.hass);
} catch (err) {
@@ -312,6 +366,7 @@ class HassioSupervisorInfo extends LitElement {
try {
await updateSupervisor(this.hass);
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
} catch (err) {
showAlertDialog(this, {
title: "Failed to update the supervisor",
@@ -426,6 +481,15 @@ class HassioSupervisorInfo extends LitElement {
justify-content: space-between;
align-items: center;
}
.card-content {
display: flex;
flex-direction: column;
height: calc(100% - 124px);
justify-content: space-between;
}
.metrics-block {
margin-top: 16px;
}
button.link {
color: var(--primary-color);
}
-185
View File
@@ -1,185 +0,0 @@
import "@material/mwc-button";
import "@material/mwc-list/mwc-list-item";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-bar";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import { fetchHassioStats, HassioStats } from "../../../src/data/hassio/common";
import { HassioHostInfo } from "../../../src/data/hassio/host";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
import {
getValueInPercentage,
roundWithOneDecimal,
} from "../../../src/util/calculate";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-system-metrics")
class HassioSystemMetrics extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@internalProperty() private _supervisorMetrics?: HassioStats;
@internalProperty() private _coreMetrics?: HassioStats;
protected render(): TemplateResult | void {
const metrics = [
{
description: "Core CPU Usage",
value: this._coreMetrics?.cpu_percent,
},
{
description: "Core RAM Usage",
value: this._coreMetrics?.memory_percent,
tooltip: `${bytesToString(
this._coreMetrics?.memory_usage
)}/${bytesToString(this._coreMetrics?.memory_limit)}`,
},
{
description: "Supervisor CPU Usage",
value: this._supervisorMetrics?.cpu_percent,
},
{
description: "Supervisor RAM Usage",
value: this._supervisorMetrics?.memory_percent,
tooltip: `${bytesToString(
this._supervisorMetrics?.memory_usage
)}/${bytesToString(this._supervisorMetrics?.memory_limit)}`,
},
{
description: "Used Space",
value: this._getUsedSpace(this.supervisor.host),
tooltip: `${this.supervisor.host.disk_used} GB/${this.supervisor.host.disk_total} GB`,
},
];
return html`
<ha-card header="System Metrics">
<div class="card-content">
${metrics.map((metric) =>
this._renderMetric(
metric.description,
metric.value ?? 0,
metric.tooltip
)
)}
</div>
</ha-card>
`;
}
protected firstUpdated(): void {
this._loadData();
}
private _renderMetric(
description: string,
value: number,
tooltip?: string
): TemplateResult {
const roundedValue = roundWithOneDecimal(value);
return html`<ha-settings-row>
<span slot="heading">
${description}
</span>
<div slot="description" title="${tooltip ?? ""}">
<span class="value">
${roundedValue}%
</span>
<ha-bar
class="${classMap({
"target-warning": roundedValue > 50,
"target-critical": roundedValue > 85,
})}"
.value=${value}
></ha-bar>
</div>
</ha-settings-row>`;
}
private _getUsedSpace = memoizeOne((hostInfo: HassioHostInfo) =>
roundWithOneDecimal(
getValueInPercentage(hostInfo.disk_used, 0, hostInfo.disk_total)
)
);
private async _loadData(): Promise<void> {
const [supervisor, core] = await Promise.all([
fetchHassioStats(this.hass, "supervisor"),
fetchHassioStats(this.hass, "core"),
]);
this._supervisorMetrics = supervisor;
this._coreMetrics = core;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
justify-content: space-between;
flex-direction: column;
display: flex;
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row > div[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
display: flex;
justify-content: space-between;
}
ha-bar {
--ha-bar-primary-color: var(
--hassio-bar-ok-color,
var(--success-color)
);
}
.target-warning {
--ha-bar-primary-color: var(
--hassio-bar-warning-color,
var(--warning-color)
);
}
.target-critical {
--ha-bar-primary-color: var(
--hassio-bar-critical-color,
var(--error-color)
);
}
.value {
width: 42px;
padding-right: 4px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-system-metrics": HassioSystemMetrics;
}
}
+5 -5
View File
@@ -13,10 +13,10 @@ import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
import "./hassio-core-info";
import "./hassio-host-info";
import "./hassio-supervisor-info";
import "./hassio-supervisor-log";
import "./hassio-system-metrics";
@customElement("hassio-system")
class HassioSystem extends LitElement {
@@ -41,6 +41,10 @@ class HassioSystem extends LitElement {
<span slot="header">System</span>
<div class="content">
<div class="card-group">
<hassio-core-info
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-core-info>
<hassio-supervisor-info
.hass=${this.hass}
.supervisor=${this.supervisor}
@@ -49,10 +53,6 @@ class HassioSystem extends LitElement {
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-host-info>
<hassio-system-metrics
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-system-metrics>
</div>
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
</div>
+7
View File
@@ -0,0 +1,7 @@
import memoizeOne from "memoize-one";
import { SupervisorArch } from "../../../src/data/supervisor/supervisor";
export const addonArchIsSupported = memoizeOne(
(supported_archs: SupervisorArch[], addon_archs: SupervisorArch[]) =>
addon_archs.some((arch) => supported_archs.includes(arch))
);
+5 -4
View File
@@ -22,6 +22,7 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@braintree/sanitize-url": "^5.0.0",
"@formatjs/intl-getcanonicallocales": "^1.4.6",
"@formatjs/intl-pluralrules": "^3.4.10",
"@fullcalendar/common": "5.1.0",
@@ -100,7 +101,7 @@
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^0.13.2",
"home-assistant-js-websocket": "^5.4.1",
"home-assistant-js-websocket": "^5.8.1",
"idb-keyval": "^3.2.0",
"intl-messageformat": "^8.3.9",
"js-yaml": "^3.13.1",
@@ -109,7 +110,7 @@
"lit-element": "^2.4.0",
"lit-html": "^1.3.0",
"lit-virtualizer": "^0.4.2",
"marked": "^1.1.1",
"marked": "2.0.0",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",
"node-vibrant": "3.2.1-alpha.1",
@@ -120,7 +121,7 @@
"resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0",
"sortablejs": "^1.10.2",
"superstruct": "^0.10.12",
"superstruct": "^0.10.13",
"tinykeys": "^1.1.1",
"unfetch": "^4.1.0",
"vis-data": "^7.1.1",
@@ -160,7 +161,7 @@
"@types/js-yaml": "^3.12.1",
"@types/leaflet": "^1.4.3",
"@types/leaflet-draw": "^1.0.1",
"@types/marked": "^1.1.0",
"@types/marked": "^1.2.2",
"@types/memoize-one": "4.1.0",
"@types/mocha": "^7.0.2",
"@types/resize-observer-browser": "^0.1.3",
+1 -1
View File
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20201229.0",
version="20210208.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
+1 -1
View File
@@ -3,9 +3,9 @@ import {
css,
CSSResult,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
+23 -9
View File
@@ -2,21 +2,25 @@ import {
css,
CSSResult,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
} from "lit-element";
import punycode from "punycode";
import { extractSearchParamsObject } from "../common/url/search-params";
import {
AuthProvider,
fetchAuthProviders,
AuthUrlSearchParams,
fetchAuthProviders,
} from "../data/auth";
import {
DiscoveryInformation,
fetchDiscoveryInformation,
} from "../data/discovery";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { registerServiceWorker } from "../util/register-service-worker";
import "./ha-auth-flow";
import { extractSearchParamsObject } from "../common/url/search-params";
import punycode from "punycode";
import("./ha-pick-auth-provider");
@@ -31,6 +35,8 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@internalProperty() private _authProviders?: AuthProvider[];
@internalProperty() private _discovery?: DiscoveryInformation;
constructor() {
super();
this.translationFragment = "page-authorize";
@@ -58,14 +64,17 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
// the name with a bold tag.
const loggingInWith = document.createElement("div");
loggingInWith.innerText = this.localize(
"ui.panel.page-authorize.logging_in_with",
this._discovery?.location_name
? "ui.panel.page-authorize.logging_in_to_with"
: "ui.panel.page-authorize.logging_in_with",
"locationName",
"LOCATION",
"authProviderName",
"NAME"
);
loggingInWith.innerHTML = loggingInWith.innerHTML.replace(
"**NAME**",
`<b>${this._authProvider!.name}</b>`
);
loggingInWith.innerHTML = loggingInWith.innerHTML
.replace("**LOCATION**", `<b>${this._discovery?.location_name}</b>`)
.replace("**NAME**", `<b>${this._authProvider!.name}</b>`);
const inactiveProviders = this._authProviders.filter(
(prv) => prv !== this._authProvider
@@ -105,6 +114,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetchAuthProviders();
this._fetchDiscoveryInfo();
if (!this.redirectUri) {
return;
@@ -126,6 +136,10 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
}
}
private async _fetchDiscoveryInfo() {
this._discovery = await fetchDiscoveryInformation();
}
private async _fetchAuthProviders() {
// Fetch auth providers
try {
+1 -1
View File
@@ -1,6 +1,6 @@
import { isComponentLoaded } from "./is_component_loaded";
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../types";
import { isComponentLoaded } from "./is_component_loaded";
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) => {
return (
+1 -1
View File
@@ -4,4 +4,4 @@ import { HomeAssistant } from "../../types";
export const isComponentLoaded = (
hass: HomeAssistant,
component: string
): boolean => hass && hass.config.components.indexOf(component) !== -1;
): boolean => hass && hass.config.components.includes(component);
+7 -3
View File
@@ -1,11 +1,15 @@
export const atLeastVersion = (
version: string,
major: number,
minor: number
minor: number,
patch?: number
): boolean => {
const [haMajor, haMinor] = version.split(".", 2);
const [haMajor, haMinor, haPatch] = version.split(".", 3);
return (
Number(haMajor) > major ||
(Number(haMajor) === major && Number(haMinor) >= minor)
(Number(haMajor) === major && Number(haMinor) >= minor) ||
(patch !== undefined &&
Number(haMajor) === major && Number(haMinor) === minor &&
Number(haPatch) >= patch)
);
};
+6
View File
@@ -34,6 +34,7 @@ export const FIXED_DOMAIN_ICONS = {
light: "hass:lightbulb",
mailbox: "hass:mailbox",
notify: "hass:comment-alert",
number: "hass:ray-vertex",
persistent_notification: "hass:bell",
person: "hass:account",
plant: "hass:flower",
@@ -77,6 +78,7 @@ export const DOMAINS_WITH_CARD = [
"input_text",
"lock",
"media_player",
"number",
"scene",
"script",
"timer",
@@ -114,6 +116,7 @@ export const DOMAINS_HIDE_MORE_INFO = [
"input_number",
"input_select",
"input_text",
"number",
"scene",
];
@@ -138,6 +141,9 @@ export const DOMAINS_TOGGLE = new Set([
"humidifier",
]);
/** Domains that have a dynamic entity image / picture. */
export const DOMAINS_WITH_DYNAMIC_PICTURE = new Set(["camera", "media_player"]);
/** Temperature units. */
export const UNIT_C = "°C";
export const UNIT_F = "°F";
+7
View File
@@ -0,0 +1,7 @@
export default function checkValidDate(date?: Date): boolean {
if (!date) {
return false;
}
return date instanceof Date && !isNaN(date.valueOf());
}
+9
View File
@@ -9,3 +9,12 @@ export const formatDate = toLocaleDateStringSupportsOptions
day: "numeric",
})
: (dateObj: Date) => format(dateObj, "longDate");
export const formatDateWeekday = toLocaleDateStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleDateString(locales, {
weekday: "long",
month: "short",
day: "numeric",
})
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
+9
View File
@@ -17,3 +17,12 @@ export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
second: "2-digit",
})
: (dateObj: Date) => format(dateObj, "mediumTime");
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locales: string) =>
dateObj.toLocaleTimeString(locales, {
weekday: "long",
hour: "numeric",
minute: "2-digit",
})
: (dateObj: Date) => format(dateObj, "dddd, HH:mm");
+2 -2
View File
@@ -3,9 +3,9 @@ import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time";
import { formatNumber } from "../string/format_number";
import { LocalizeFunc } from "../translations/localize";
import { computeStateDomain } from "./compute_state_domain";
import { formatNumber } from "../string/format_number";
export const computeStateDisplay = (
localize: LocalizeFunc,
@@ -63,7 +63,7 @@ export const computeStateDisplay = (
if (domain === "humidifier") {
if (compareState === "on" && stateObj.attributes.humidity) {
return `${stateObj.attributes.humidity}%`;
return `${stateObj.attributes.humidity} %`;
}
}
+1 -1
View File
@@ -1,9 +1,9 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE } from "../../data/entity";
import { HomeAssistant } from "../../types";
import { DOMAINS_WITH_CARD } from "../const";
import { canToggleState } from "./can_toggle_state";
import { computeStateDomain } from "./compute_state_domain";
import { UNAVAILABLE } from "../../data/entity";
export const stateCardType = (hass: HomeAssistant, stateObj: HassEntity) => {
if (stateObj.state === UNAVAILABLE) {
+9 -17
View File
@@ -1,7 +1,7 @@
import Vibrant from "node-vibrant/lib/browser";
import MMCQ from "@vibrant/quantizer-mmcq";
import { BasicPipeline } from "@vibrant/core/lib/pipeline";
import { Swatch, Vec3 } from "@vibrant/color";
// We import the minified bundle because the unminified bundle
// has some quirks that break wds. See #7784 for unminified version.
import Vibrant from "node-vibrant/dist/vibrant";
import type { Swatch, Vec3 } from "@vibrant/color";
import { getRGBContrastRatio } from "../color/rgb";
const CONTRAST_RATIO = 4.5;
@@ -104,23 +104,15 @@ const customGenerator = (colors: Swatch[]) => {
}
return {
foreground: new Swatch(foregroundColor, 0),
// We can't import Swatch constructor from the minified bundle, take it from background color.
// @ts-expect-error
foreground: new backgroundColor.constructor(foregroundColor, 0),
background: backgroundColor,
};
};
Vibrant.use(
new BasicPipeline().filter
.register(
"default",
(r: number, g: number, b: number, a: number) =>
a >= 125 && !(r > 250 && g > 250 && b > 250)
)
.quantizer.register("mmcq", MMCQ)
// Our generator has different output
// @ts-expect-error
.generator.register("default", customGenerator)
);
// Set our custom generator as the default.
Vibrant._pipeline.generator.register("default", customGenerator);
export const extractColors = (url: string, downsampleColors = 16) =>
new Vibrant(url, {
+2 -2
View File
@@ -1,3 +1,5 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiClose, mdiMagnify } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import {
css,
@@ -10,8 +12,6 @@ import { html, TemplateResult } from "lit-html";
import { classMap } from "lit-html/directives/class-map";
import "../../components/ha-svg-icon";
import { fireEvent } from "../dom/fire_event";
import { mdiMagnify, mdiClose } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
@customElement("search-input")
class SearchInput extends LitElement {
+45
View File
@@ -0,0 +1,45 @@
import { StructError } from "superstruct";
import type { HomeAssistant } from "../../types";
export const handleStructError = (
hass: HomeAssistant,
err: Error
): { warnings: string[]; errors?: string[] } => {
if (!(err instanceof StructError)) {
return { warnings: [err.message], errors: undefined };
}
const errors: string[] = [];
const warnings: string[] = [];
for (const failure of err.failures()) {
if (failure.value === undefined) {
errors.push(
hass.localize(
"ui.errors.config.key_missing",
"key",
failure.path.join(".")
)
);
} else if (failure.type === "never") {
warnings.push(
hass.localize(
"ui.errors.config.key_not_expected",
"key",
failure.path.join(".")
)
);
} else {
warnings.push(
hass.localize(
"ui.errors.config.key_wrong_type",
"key",
failure.path.join("."),
"type_correct",
failure.type,
"type_wrong",
JSON.stringify(failure.value)
)
);
}
}
return { warnings, errors };
};
@@ -1,4 +1,4 @@
import { StructResult, StructContext, struct } from "superstruct";
import { struct, StructContext, StructResult } from "superstruct";
const isEntityId = (value: unknown, context: StructContext): StructResult => {
if (typeof value !== "string") {
@@ -1,4 +1,4 @@
import { StructContext, StructResult, struct } from "superstruct";
import { struct, StructContext, StructResult } from "superstruct";
const isIcon = (value: unknown, context: StructContext): StructResult => {
if (typeof value !== "string") {
+1 -1
View File
@@ -1,5 +1,5 @@
import IntlMessageFormat from "intl-messageformat";
import { shouldPolyfill } from "@formatjs/intl-pluralrules/should-polyfill";
import IntlMessageFormat from "intl-messageformat";
import { Resources } from "../../types";
export type LocalizeFunc = (key: string, ...args: any[]) => string;
+13
View File
@@ -6,3 +6,16 @@ export const extractSearchParamsObject = (): Record<string, string> => {
}
return query;
};
export const extractSearchParam = (param: string): string | null => {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(param);
};
export const createSearchParam = (params: Record<string, string>): string => {
const urlParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
urlParams.append(key, value);
});
return urlParams.toString();
};
+1 -2
View File
@@ -7,10 +7,9 @@ import {
html,
LitElement,
property,
TemplateResult,
query,
TemplateResult,
} from "lit-element";
import "../ha-circular-progress";
@customElement("ha-progress-button")
-1
View File
@@ -1,5 +1,4 @@
import { wrap } from "comlink";
import type { api } from "./sort_filter_worker";
type FilterDataType = api["filterData"];
+3 -3
View File
@@ -1,11 +1,11 @@
// @ts-nocheck
import Vue from "vue";
import wrap from "@vue/web-component-wrapper";
import { customElement } from "lit-element/lib/decorators";
import Vue from "vue";
import DateRangePicker from "vue2-daterange-picker";
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
import { fireEvent } from "../common/dom/fire_event";
import { Constructor } from "../types";
import { customElement } from "lit-element/lib/decorators";
const Component = Vue.extend({
props: {
@@ -210,7 +210,7 @@ class DateRangePickerElement extends WrappedElement {
}
.calendar-table {
padding: 0 !important;
}
}
`;
const shadowRoot = this.shadowRoot!;
shadowRoot.appendChild(style);
@@ -1,5 +1,6 @@
import "@material/mwc-icon-button/mwc-icon-button";
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
@@ -11,9 +12,9 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
TemplateResult,
} from "lit-element";
@@ -37,9 +38,8 @@ import {
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "./ha-devices-picker";
import "../ha-svg-icon";
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
import "./ha-devices-picker";
interface DevicesByArea {
[areaId: string]: AreaDevices;
@@ -6,9 +6,9 @@ import {
css,
CSSResult,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../common/dom/fire_event";

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