Compare commits

...

584 Commits

Author SHA1 Message Date
Paulus Schoutsen
77b37bce7e Fix more info dialog resizing 2020-02-05 14:01:23 -08:00
Zack Arnett
2e4c73c087 Convert Media Control Card to Typescript (#4761)
* Delete old JS file. Add new TS file. Add Type

* Updates

* Reviews

* Review Updates :)

* Updating State Localize

* Clean up

* Fixing Travis
2020-02-05 09:28:29 -08:00
Bram Kragten
c7f7ef28bf Fixes removing audio device (#4763)
(only the supervisor doesn't support it)
2020-02-05 09:21:43 -08:00
Michael Irigoyen
aac7dbab58 Update Material Design Icons to v4.9.95 (#4764) 2020-02-05 17:34:10 +01:00
Bram Kragten
8518f774d4 Rename hass.io panel to supervisor 2020-02-05 10:00:19 +01:00
HomeAssistant Azure
cb0d91d124 [ci skip] Translation update 2020-02-05 00:32:34 +00:00
Zack Arnett
107f428dd3 Sensor Card Fill feature (#4745) 2020-02-04 16:11:13 -08:00
Bram Kragten
7758ddba56 Fix theming with derived styles (#4758)
* Fix theming with derived styles

* Move
2020-02-04 15:40:35 -08:00
Paulus Schoutsen
e0376c803f Fix hassio audio 2020-02-04 10:28:43 -08:00
Paulus Schoutsen
788c490bbc Update update headers in hassio (#4751)
* Update update headers in hassio

* Other tabs too
2020-02-04 18:36:10 +01:00
Ian Richardson
cdf6e9eb75 🐛 properly format timestamps in glance card (#4602)
* 🐛 properly format timestamps in glance card

* address review comments
2020-02-04 16:18:42 +01:00
Bram Kragten
4aa49f66bc Remove unused classmap 2020-02-04 15:55:29 +01:00
Bram Kragten
1bf82f216a Tweak badge focus padding (#4750) 2020-02-04 15:15:15 +01:00
Bram Kragten
004ff58c21 Update hassio-style.ts 2020-02-04 13:34:14 +01:00
Joakim Sørensen
f1a1654371 Use icon image where available (#4721)
* Use iconimg where available

* Adjust margin for icon

* remove log

* Fix property casing

* Add blue topbar, and generalize properties

* Inline checks

* inline property functions

* Limit compute

* Linting

* lovercase const

* Review comments and move update dot to line

* Add roboto font to hassio

* Fix update and stopped styles colliding

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-02-04 12:36:09 +01:00
Bram Kragten
862044ca23 Hassio styling tweaks (#4749)
* Hassio styling tweaks

* Update hassio-style.ts

* Update hassio-pages-with-tabs.ts
2020-02-04 10:54:10 +01:00
Paulus Schoutsen
c54b474838 Hide automations from generated UI (#4748) 2020-02-04 09:27:26 +01:00
Bram Kragten
42cbe863bb Fix passive color radius and fix switch label clicks (#4703)
* Fix passive color radius and fix switch label clicks

* Colors vars
2020-02-03 22:32:22 -08:00
HomeAssistant Azure
ccc42dad79 [ci skip] Translation update 2020-02-04 00:33:02 +00:00
Paulus Schoutsen
82ff444cec Confirm when resetting hassio optoins (#4718) 2020-02-03 08:53:57 -08:00
Paulus Schoutsen
24c591fbf3 Convert Hass.io add-on options to YAML (#4717)
* Convert Hass.io options to YAML

* Fix yaml editors on other places

* Update ha-automation-action-service.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-02-03 11:06:47 +01:00
Joakim Sørensen
ad676d7fd3 Fix theme on sensor card (#4724) 2020-02-03 09:30:20 +01:00
David F. Mulcahey
cbe4782d78 fix loading groups (#4727) 2020-02-03 09:29:13 +01:00
HomeAssistant Azure
3fdcc1c0ea [ci skip] Translation update 2020-02-03 00:32:48 +00:00
HomeAssistant Azure
f9d64e51c4 [ci skip] Translation update 2020-02-02 00:32:38 +00:00
Paulus Schoutsen
b082828a75 Forward haptic events to parent window (#4719) 2020-02-01 08:11:57 -08:00
Paulus Schoutsen
25f5bf0042 Fix ingress add-on not started dialog showing twice (#4716)
* Fix ingress add-on not started dialog showing twice

* logging not allowed
2020-02-01 08:11:30 -08:00
Bram Kragten
f5dec3c6d5 Change map settings icon (#4701)
* Change map settings icon

Closes #4694

* hide for demo
2020-02-01 00:26:22 -08:00
Joakim Sørensen
3215437bb8 Fixes add-on audio selector (#4648)
* Fixes addon audio selector

* Set device as property

* Use getAttribute

* Keep device as attribute

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2020-01-31 23:03:30 -08:00
HomeAssistant Azure
33176d8f3d [ci skip] Translation update 2020-02-01 00:32:28 +00:00
Ian Richardson
f82b62f45c 🐛 fix tabindex for default entity more-info actions (#4697)
* 🐛 fix tabindex for default entity more-info actions

* Update hui-state-label-badge.ts
2020-01-31 17:19:08 +01:00
Bram Kragten
edfdd0da89 Styling zone menu (#4684)
* Styling zone menu

* Update ha-device-entities-card.ts
2020-01-31 12:14:21 +01:00
HomeAssistant Azure
33d9bf4660 [ci skip] Translation update 2020-01-31 00:32:34 +00:00
Bram Kragten
1912bda60d Fix flex issues on google and alexa settings page (#4674) 2020-01-30 17:18:27 +01:00
Bram Kragten
2e25db4d1b Merge pull request #4673 from home-assistant/fix-tooltip
Fix tooltip in sidemenu
2020-01-30 17:18:02 +01:00
Bram Kragten
ec08b2ef65 Update ha-sidebar.ts 2020-01-30 16:39:09 +01:00
Bram Kragten
2c740cedb8 Fix tooltip sidemenu 2020-01-30 16:38:42 +01:00
Bram Kragten
e3984d7bf9 Bumped version to 20200130.0 2020-01-30 15:51:24 +01:00
Bram Kragten
2f86b6ec3d Merge pull request #4671 from home-assistant/device-page-fixes
Device info page fixes
2020-01-30 14:51:40 +01:00
Bram Kragten
d10045eac1 Remove log 2020-01-30 14:34:53 +01:00
Bram Kragten
41da68290e Merge pull request #4668 from home-assistant/zone-fixes
Zone ui fixes
2020-01-30 14:04:36 +01:00
Bram Kragten
593a2de07b Device info page fixes
Fixes #4666
Fixes #4664
Fixes #4663
Fixes #4662
2020-01-30 14:03:45 +01:00
Bram Kragten
59540bdf63 Merge pull request #4667 from home-assistant/restore-back-buttons
Restore back buttons
2020-01-30 11:56:29 +01:00
Bram Kragten
616df070a4 Zone ui fixes
fixes #4659
fixes #4658
fixes #4661
fixes #4665
fixes #4660
fixes #4655
2020-01-30 11:54:49 +01:00
Bram Kragten
ef62f1956b Restore back buttons
fixes #4654
fixes #4653
fixes #4656
2020-01-30 11:22:50 +01:00
HomeAssistant Azure
b41f25ef12 [ci skip] Translation update 2020-01-30 00:32:32 +00:00
Bram Kragten
3ed538276e Merge branch 'master' into dev 2020-01-29 19:33:36 +01:00
Bram Kragten
c1a29e8091 Bumped version to 20200129.0 2020-01-29 19:28:09 +01:00
Bram Kragten
4e8bf434f1 Add settings button to map panel (#4641) 2020-01-29 19:04:12 +01:00
Bram Kragten
dd8c568a2c Fix action directive double tab iOS issues (#4639) 2020-01-29 19:03:43 +01:00
Bram Kragten
7ab9257f5e Fix translation advanced mode (#4647) 2020-01-29 19:01:35 +01:00
Bram Kragten
7021fd5809 Make dialogs full width on mobile, and modal (#4642)
* Make dialogs full width on mobile, and modal

* Fix dialog to bottom screen mobile
2020-01-29 08:59:37 -08:00
Bram Kragten
adec2fc2df Only update disabled for entity reg if it is a user value. (#4646) 2020-01-29 08:51:29 -08:00
Bram Kragten
27ebcc1bda Remove balloob from cast page (#4645) 2020-01-29 17:09:56 +01:00
HomeAssistant Azure
2fb7a31c76 [ci skip] Translation update 2020-01-29 00:32:22 +00:00
Bram Kragten
65994e7280 Add related items to device info (#4637)
* Add related items to device info

* Update ha-config-device-page.ts

* remove log

* Lint

* Fix dialog logic showing triggers on script dialog

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2020-01-28 15:13:44 -08:00
Joakim Sørensen
036eedc69d Add styles to addon-header (#4632)
* Add styles to addon-header

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2020-01-28 14:37:53 -08:00
Joakim Sørensen
7937714ce6 Fixes add-on install button (#4635)
* Fixes add-on install button

* Set the default

* linting issue
2020-01-28 14:37:42 -08:00
Bram Kragten
cdbd51f6f7 Update hass-tabs-subpage.ts 2020-01-28 22:44:21 +01:00
Bram Kragten
d5a8105718 Update ha-config-areas.ts 2020-01-28 22:40:57 +01:00
Bram Kragten
1c9eab7ca0 Change config navigation to tabs (#4630)
* Change config navigation to tabs

* Update ha-menu-button.ts

* Icons

* update

* Review comments

* configSections -> object instead of array
2020-01-28 21:48:21 +01:00
Joakim Sørensen
6e624b394b Corrects add-on link (#4634)
* Fixes the addon url

* quote it

* Don't link the extra space

* puerly for testing

* Don't link the extra space
2020-01-28 11:39:42 -08:00
Joakim Sørensen
1cb10694e7 Fixes status switch spacing (#4633)
* fix switch spacing

* Fix alignment
2020-01-28 11:37:33 -08:00
HomeAssistant Azure
d496b9742f [ci skip] Translation update 2020-01-28 00:32:38 +00:00
Paulus Schoutsen
72e5375795 Fix hassio build 2020-01-27 11:57:28 -08:00
Bram Kragten
04f8f0f74f Render should always return a html template (#4612) 2020-01-27 08:34:22 -08:00
Paulus Schoutsen
82fb622904 Add buttons header-footer (#4601)
* Add buttons header-footer

* Simplify

* Update src/panels/lovelace/header-footer/hui-buttons-header-footer.ts

Co-Authored-By: Ian Richardson <iantrich@gmail.com>

* Address comments

Co-authored-by: Ian Richardson <iantrich@gmail.com>
2020-01-27 09:34:08 +01:00
HomeAssistant Azure
95ba1fd0cb [ci skip] Translation update 2020-01-27 00:32:22 +00:00
Bram Kragten
c7b3a517e8 Add area picker (#4608)
* Add area picker

* Make config flow dialog wider

* Comment
2020-01-26 21:41:11 +01:00
Joakim Sørensen
523dc881bb Convert Hass.io panel to TS/Lit (#4398)
* Convert system

* Convert dashboard

* Remove logging statement

* Convert addon view (base) and log

* Convert addon-view info

* Remove unintended file in commit

* Convert ansi-to-html

* Fix log update reloading

* Convert addon-view config

* Convert addon-view network

* Add inn missing haStyle

* Convert addon-view audio

* convert dialog-hassio-markdown

* Convert dialog-hassio-snapshot

* Convert entrypoint

* Convert hassio-style

* Lint hassio-addon-audio

* Lint hassio-addon-audio

* Lint hassio-addon-config

* Remove file that should not have been comitted

* Linting of the rest

* Cleanup

* Cleanup config

* Required changes after rebase

* Change property/method clasification

* use ? for _inputDevices and _outputDevices

* Use undefined instead of null for addon property

* Use ? for addons property

* Async addon audio

* Corrects typo in Error

* Wrap async calls in try/catch

* Remove npm task

* Fix async constant/functions

* Reintroduce noDevice

* We don't use the data of the POST no need to store and pass it

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Update hassio/src/addon-view/hassio-addon-config.ts

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Update hassio/src/addon-view/hassio-addon-audio.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-audio.ts

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Apply review comments

* Simplify selected item change

* Change back to attr

* Apply lessons learned to addon-config

* Send event on config change

* Extract error msg

* Apply lessons learned to addon-info

* Apply lessons learned to addon-logs

* Fix shorthand linting issue

* Prefix private with _

* reset error

* Apply lessons learned to addon-network

* Revert package.json change

* Apply lessons learned to addon-view

* Fixes Unnecessary 'await' issue

* rename content -> addoninfo

* Update hassio/src/addon-view/hassio-addon-config.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-config.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-config.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-network.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-logs.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Fix syntax issues

* Fix error handling issues

* Use forEach and not map

* Use private for _error

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update hassio/src/addon-view/hassio-addon-info.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Use classMap

* remove unneded limitations

* it can be null

* Update hassio/src/system/hassio-supervisor-log.ts

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* split hassio.ts

* Update datahandling

* Return result

* Use map instead of forEach

* Unnecessary 'await'.

* Move setSupervisorOption to data/hassio/supervisor

* Unnecessary 'await'

* Move fetchSupervisorLogs to data/hassio/supervisor

* Move fetchHassioHardwareInfo to data/hassio/hardware

* change error property

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-26 20:37:20 +01:00
HomeAssistant Azure
1123adc584 [ci skip] Translation update 2020-01-26 00:32:39 +00:00
Timmo
15be1688ad Merge Confirmation Dialogs into Alert, Confirmation or Prompt (#4114)
*  Add alert dialog

🔨 Add alert dialogs

🔨 add more

🔨 Add more

🔨 Enhance check

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

🔥 Combine confirmation into alert dialog

🔨 Replace confirmation calls with alert dialog

✏️ Remove 3

🔨 Add prompt logic

Rename to generic

Rename and add new params for alert, confirmation and prompt

Renames and prop changes

Rename and props

Cleanup

Setup prompt

Wording

Use text for prompt

Add prompts and confirmation on delete user

Rename

Rename

Rename imports

Fix parms change

Only use default for confirmation

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

Update src/dialogs/generic/dialog-box.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

Update

change text

Add autofocus

Merge show dialogs into one generic

Add automation delete confirmation

Modal

Remove deleted file

Add delete donfirm to script

Fix error with tslint

Fix from rebase

Fix from rebase

Fix from rebase

* 🔧 Split dialog functions

* 🔧 Fix from rebase

* 🔧 More fixes

* 🔧 Fix

* 🔧 Apply suggestions from code review

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* 🔧 Update from suggestion

* 🔧 Renames and cleanup

* 🔧 Camelcase

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* 🔧 camel case

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-25 17:48:29 +01:00
Bram Kragten
0c0e82a3ba Move get dialog logic entity registry (#4579) 2020-01-25 14:49:36 +01:00
Paulus Schoutsen
8abe8d7615 Automatically strip area name from entities (#4597) 2020-01-25 14:43:11 +01:00
Paulus Schoutsen
f95ba4c04c Allow options flow to define form title/description (#4598)
* Allow options flow to define their own title

* Allow description too
2020-01-25 14:38:58 +01:00
Ian Richardson
6874788cc0 🐛 fix severity toggle in gauge card editor (#4600) 2020-01-25 14:35:48 +01:00
David F. Mulcahey
f77bd79387 fix user given name display (#4606) 2020-01-25 14:34:15 +01:00
Ian Richardson
67a91b7c19 💄 color lights unless explicitly set not to (#4603) 2020-01-24 22:21:31 -08:00
HomeAssistant Azure
30211fe61d [ci skip] Translation update 2020-01-25 00:32:57 +00:00
Bram Kragten
9de80b2947 Fix menu button for touch (#4589)
* Fix menu button for touch

* Missed 2
2020-01-24 23:21:41 +01:00
Bram Kragten
b7a3fe6e91 Improve zones UI (#4576)
* Improve zones UI

* Make home blue

* Only fit after edit in dialog

* Filter states from UI edtor

* Comments

* bring editable circle to front

* Update ha-config-zone.ts

* Update ha-config-zone.ts

* Hide side navigation for zones

* Fix initial ignore fit, fix click on switch label, space dialog buttons
2020-01-24 23:21:26 +01:00
Ian Richardson
1f38d13b3b add action_name option for scene/script rows (#4571)
*  add action_name option for scene/script rows

* address review comments
2020-01-24 23:01:01 +01:00
Ian Richardson
ef6e468a7f 💄 align light card regardless of brightness support (#4582)
* 💄 align light card regardless of brightness support

* merge css properties
2020-01-24 22:12:54 +01:00
Ian Richardson
729a5e385f 🐛 don't remove key when value is empty (#4583)
* 🐛 don't remove key when value is empty

* only restrict delete to required field
2020-01-24 22:12:14 +01:00
Bram Kragten
32fd7a51f4 Fix entity registry dialog translations (#4575)
Fixes #4567
2020-01-24 10:09:40 -08:00
Ian Richardson
f3f32c800e 💄 move advanced mode toggle (#4587) 2020-01-24 09:50:20 -08:00
Bram Kragten
1f44b4b5a9 Fit map if an entity moved (#4590) 2020-01-24 15:12:21 +01:00
Bram Kragten
971538e9c2 Fix alarm card editor (#4592) 2020-01-24 15:11:57 +01:00
Bram Kragten
db9924bd87 Fix Mobile Entity Registry Delete Button (#4591) 2020-01-24 13:59:43 +01:00
Ian Richardson
74c6b9077a add conditional row (#4569)
*  add conditional row

* fix types

* address comments
2020-01-24 09:59:36 +01:00
Ian Richardson
23865c31e6 🐛 fix editor switches (#4584) 2020-01-24 09:32:56 +01:00
Ian Richardson
8641a701cb revert fix for card editor overflow menu selection (#4585) 2020-01-24 09:32:14 +01:00
HomeAssistant Azure
3ca99e5c90 [ci skip] Translation update 2020-01-24 00:32:23 +00:00
Ian Richardson
753804f463 💄 additional active icon states (#4510)
* 💄 additional active icon states

* ♻️ sort by domain

* 👌 address review comments

* state_color option for entities card

* fix typo

* 👌 address comments

extract common css
properly set boolean attributes
separate error states/color
fix unavailable color

* only make copy if necessary

* remove paused timer

* remove paused timer
2020-01-23 09:01:18 -08:00
Ian Richardson
9aedeab4fa close card editor on ESC (#4570) 2020-01-23 14:43:09 +01:00
HomeAssistant Azure
fc4e3e90b2 [ci skip] Translation update 2020-01-23 00:32:51 +00:00
Bram Kragten
ae8a9940ed Add state and related to entity reg dialog (#4473)
* Add state and related to entity reg dialog

* Replace more-info-settings, remove state tab add state button
2020-01-22 15:03:47 -08:00
Pascal Vizeli
a544295167 Add netlify pipeline (#4563)
* Add netlify pipeline

* Address comments
2020-01-22 14:42:32 -08:00
Ian Richardson
8a9e149d33 ♻️ convert more-info-script to LitElement/TypeScript (#4545)
* ♻️ convert more-info-script to Lit/TypeScript

* ♻️ convert more-info-script to LitElement/TypeScript

* 👌 use relative time
2020-01-22 22:01:02 +01:00
Bram Kragten
49611e285f Add zones config UI (#4556)
* Add zones config UI

* Update en.json

* Update dialog-zone-detail.ts

* Update hc-cast.ts

* Update more-info-content.ts

* Add drag radius and icon to dialog

* Review comments
2020-01-22 11:29:51 -08:00
Franck Nijhof
def0c51669 Update issue and PR templates (#4553)
* Update issue and PR templates

* Small Tweak
2020-01-22 09:33:18 -08:00
Ian Richardson
572215b359 🔥 remove more-info-updater (#4544)
* 🔥 remove more-info-updater

* remove reference
2020-01-22 09:30:12 -08:00
Bram Kragten
0f8cf574d3 Use state icon in logbook instead of domain icon (#4558)
Fixes https://github.com/home-assistant/home-assistant-polymer/issues/2313
2020-01-22 09:17:49 -08:00
Bram Kragten
312f1df368 Fix string type in ha-form-string (#4559) 2020-01-22 15:28:54 +01:00
Bram Kragten
5bfacb3bf0 Fix lovelace reload button on error page (#4557)
Fixes https://github.com/home-assistant/home-assistant-polymer/issues/3107
2020-01-22 15:03:51 +01:00
HomeAssistant Azure
2103866c48 [ci skip] Translation update 2020-01-22 00:32:29 +00:00
Franck Nijhof
61cf1bf1eb Add stalebot (#4551) 2020-01-21 16:51:10 +01:00
David F. Mulcahey
9c407caf2c Hide blank manufacturer names on Device details page (#4543)
* don't show "by" when there is no manufacturer

* Update src/panels/config/devices/device-detail/ha-device-card.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-21 16:26:32 +01:00
Bram Kragten
323cf72be3 Fix #4539 (#4550) 2020-01-21 16:25:58 +01:00
Marius
eeced628b3 add never_triggered (#4442)
* add never_triggered

Add never_triggered to the last-triggered secondary_info.
as it stands, the secondary_info line is empty when used on an automation or script that hasn't been triggered yet, while the user expects info on that line.

* add "never_triggered"

for automation and script, to use in secondary_info
add last_triggered on script, because it was missing

* delete spaces

* add 2 spaces

travis check

* travis check row 116

* add never_triggered

to entities, delete incorrect entries to automation and script

* Update src/translations/en.json

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/lovelace/components/hui-generic-entity-row.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/lovelace/components/hui-generic-entity-row.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update en.json

* Update hui-generic-entity-row.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Ian Richardson <iantrich@gmail.com>
2020-01-21 08:21:32 -06:00
David F. Mulcahey
38be488f86 Update ZHA config panel to handle the addition of ZHA devices for the Zigbee coordinator (#4541)
* only show when there are entities

* handle coordinator correctly
2020-01-21 10:35:31 +01:00
Ian Richardson
83756a338a ♻️ convert more-info-automation to LitElement/TypeScript (#4547) 2020-01-21 10:31:22 +01:00
Ian Richardson
b9415cb5f0 ♻️ convert more-info-default to LitElement/TypeScript (#4546) 2020-01-21 10:29:38 +01:00
HomeAssistant Azure
fd49e26120 [ci skip] Translation update 2020-01-21 00:32:30 +00:00
Bram Kragten
3474e92eb7 Fix alignment entities card header toggle (#4532) 2020-01-20 21:05:05 +01:00
Bram Kragten
913299998c Fix missing text device entities (#4531) 2020-01-20 21:04:47 +01:00
Paulus Schoutsen
d9e522e4d7 Add header/footer support to entities card (#4496)
* Add header/footer support to entities card

Oops debug radius

Revert change to hui-picture-card

Remove unused import

* Use new createElement helper
2020-01-20 18:13:19 +01:00
David F. Mulcahey
22c8e4a455 Add the "add all device entities to Lovelace" functionality to ZHA device card (#4521)
* fix race condition

* add all entities
2020-01-20 14:22:48 +01:00
Franck Nijhof
02fe5144d8 Remove obsolete homebridge_* attributes (#4533) 2020-01-20 14:17:57 +01:00
David F. Mulcahey
9d333fb557 use logical device type (#4524) 2020-01-20 12:14:47 +01:00
David F. Mulcahey
ef2ca4a07f Clean up display of ZHA device page (#4522)
* remove margins

* adjust help icon and max width
2020-01-20 12:13:47 +01:00
Ian Richardson
f74ee76ae2 📝 clarify what conditions in automations are (#4514)
* 📝 clarify what conditions in automations are

* 📝 reword to make short and sweet
2020-01-19 17:07:58 -08:00
HomeAssistant Azure
6fb9a7636d [ci skip] Translation update 2020-01-20 00:32:40 +00:00
Joakim Sørensen
56249110d6 Adds error to shopinglist card editor if integration is not loaded (#4523)
* Adds error to shopinglist card edito if integration is not loaded

* localization

* Update src/translations/en.json

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2020-01-19 14:39:07 -08:00
Ian Richardson
1a2ebabd22 🚸 prompt user to confirm logout (#4501)
* 🚸 prompt user to confirm logout

* 👌 update dialog title/text/confirm
2020-01-18 22:02:36 -08:00
Ian Richardson
28511d0cbf only set tabindex if tap_action defined (#4511) 2020-01-18 21:59:56 -08:00
Joakim Sørensen
b01bdec973 Bring back ha-icon-next (#4516) 2020-01-18 21:57:24 -08:00
Michael Irigoyen
ecee5980af Update Material Design Icons to v4.8.95 (#4520) 2020-01-18 21:54:30 -08:00
HomeAssistant Azure
3e1b85e6b5 [ci skip] Translation update 2020-01-19 00:32:50 +00:00
HomeAssistant Azure
ce94d1ea7c [ci skip] Translation update 2020-01-18 00:32:47 +00:00
Paulus Schoutsen
62654ec598 Refactor create element (#4509)
* Refactor create element

* Move things around

* Fix reference in gallery
2020-01-17 16:19:01 -08:00
Ian Richardson
fbe4550c78 🐛 fix display of current/set temperature when 0 (#4498)
* 🐛 fix display of current/set temperature when 0

* 👌 nullish coalescing
2020-01-17 23:08:18 +01:00
Bram Kragten
a082667c24 Bumped version to 20200108.2 2020-01-17 22:42:48 +01:00
Bram Kragten
fc29b519ae Don't use light card in generated Lovelace mode (#4505)
* Don't make seperate card for grouped lights

* Update generate-lovelace-config.ts

* Don't use light group at all in generated UI
2020-01-17 22:42:24 +01:00
Bram Kragten
c391b19c0e fixes #4468 (#4508) 2020-01-17 22:41:57 +01:00
Bram Kragten
8c49e3c4ef fixes #4468 (#4508) 2020-01-17 22:41:09 +01:00
Ian Richardson
67022b9ffc 🐛 make picture card tabbable only if action set (#4494)
* 🐛 make picture card tabbable only if action set

* 👌use ifDefined and hasAction

* ✏️ fix syntax mistake
2020-01-17 22:39:24 +01:00
Bram Kragten
eb9023595d Don't use light card in generated Lovelace mode (#4505)
* Don't make seperate card for grouped lights

* Update generate-lovelace-config.ts

* Don't use light group at all in generated UI
2020-01-17 22:38:08 +01:00
Bram Kragten
8262d1617f Bumped version to 20200108.1 2020-01-17 17:13:00 +01:00
Alexei Chetroi
581a803cc4 Fix ZHA add device path. (#4486) 2020-01-17 17:12:13 +01:00
Paulus Schoutsen
5ff8fe68ba Alert user when add-on not started (#4485) 2020-01-17 17:02:56 +01:00
David F. Mulcahey
a2a039ebc5 Add group binding to the ZHA config panel and misc. cleanup (#4466)
* clean up zha device card and usage

* group binding tile

* add cluster selection to group binding tile

* fix css class name

* fix filtering

* multiselect for clusters in group binding

* pass narrow to cluster table

* fix tables

* fix device page

* address remaing comments from previous PR

* fix bad cherry-pick

* css cleanup

* consistency

* use properties

* translations

* add confirmation dialog to remove button

* fix css

* review comments

* remove noise
2020-01-17 16:39:57 +01:00
Ian Richardson
1064aed1b0 📝 make some Lovelace UI text more clear (#4500) 2020-01-17 09:54:16 +01:00
Ian Richardson
7025592e8e 🐛 fix picture glance card's camera_view option in editor (#4495) 2020-01-17 09:44:57 +01:00
HomeAssistant Azure
4966354b62 [ci skip] Translation update 2020-01-17 00:32:37 +00:00
David F. Mulcahey
68d6faf4af fix selection check (#4488) 2020-01-16 18:19:01 +01:00
Paulus Schoutsen
e3346483b9 Hide device trackers from generated lovelace (#4487) 2020-01-16 08:57:41 +01:00
HomeAssistant Azure
e8fb79e5ce [ci skip] Translation update 2020-01-16 00:32:40 +00:00
Alexei Chetroi
d612162ab1 Fix ZHA add device path. (#4486) 2020-01-15 20:05:05 +01:00
Bram Kragten
86f8ef3a70 Styling focus menus (#4483)
* Styling menus

* Update ha-config-navigation.ts
2020-01-15 19:41:56 +01:00
Bram Kragten
0e43435362 Don't ask to choose view when only 1 view (#4480) 2020-01-15 09:05:01 -08:00
Bram Kragten
aaefe0b09f Handle unknown state (#4481) 2020-01-15 09:01:59 -08:00
Bram Kragten
bc731a9dc3 Add edit btn to more info for scene, script and automation (#4476) 2020-01-15 09:50:16 +01:00
Bram Kragten
da25701dca Disable adoptedStyleSheets in dev (#4474) 2020-01-15 09:25:17 +01:00
Bram Kragten
21ae483dc9 Styling fixes (#4475) 2020-01-15 09:25:04 +01:00
HomeAssistant Azure
38b6e9ca10 [ci skip] Translation update 2020-01-15 00:32:57 +00:00
Bram Kragten
d31245866c Add DEPRECATED to states ui (#4463)
* Add DEPRECATED to states ui

* unelevated red

* target

* Add msg in info
2020-01-14 06:35:01 -08:00
Bram Kragten
4e08d8f3b3 Fix zha back btn (#4470) 2020-01-14 07:57:00 -05:00
Bram Kragten
1e717ab33e Catch undefined cloudstatus (#4465) 2020-01-14 13:52:23 +01:00
Bram Kragten
995fb4974e Fix translations (#4469) 2020-01-14 13:20:06 +01:00
HomeAssistant Azure
ffb76132f8 [ci skip] Translation update 2020-01-14 00:32:29 +00:00
Bram Kragten
acba3af54b Fix back btn for Polymer (#4467) 2020-01-13 18:21:43 +01:00
Paulus Schoutsen
40ac456937 Force refresh tokens if external app (#4461) 2020-01-13 05:47:08 -08:00
Bram Kragten
5c32413bf7 Onboarding core: Display error message when saving fails (#4462) 2020-01-13 05:31:53 -08:00
Bram Kragten
22792c70c5 Change config panel navigation (#4377)
* Change config panel navigation

* Show active + don't show toolbar?

* Update ha-panel-config.ts

* Change color of menu toolbar

* Update ha-config-router.ts

* Review comments
2020-01-12 17:57:38 +01:00
Krisjanis Lejejs
a8ed87298a Improved map panel and map card to ignore zones when fitting map. (#4447)
* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Changed approach and created a different array for zones

* Removed zone key option for markers
2020-01-12 17:56:55 +01:00
Joakim Sørensen
b15270dfe2 Use correct suffix for elevation (#4454)
* Use correct suffix for elevation

* Use correct suffix for elevation
2020-01-12 07:31:59 -08:00
Bram Kragten
58ad949bc8 Virtualize logbook (#4450)
* Virtualize logbook

* Clean

* Update ha-logbook.ts
2020-01-12 13:00:26 +01:00
HomeAssistant Azure
adce40de56 [ci skip] Translation update 2020-01-12 00:33:31 +00:00
Ian Richardson
0f487ae4bf Add tabindex to lovelace elements (#4160)
* tabindex

* use action handler

* circular focus test

* address comment

* add focus styling to other elements

* add focus styling to cards

* style glance card entities

* Add back light/thermo changes that were lost in rebase

* Remove unused import

* lint

* lint

* 💄 tweak focus style for glance entities

* 💄 apply styling to focused state-label-badges
2020-01-11 11:50:43 +01:00
Joakim Sørensen
2848e3a63b Adds CCS var usage to person dialog (#4449) 2020-01-11 11:49:57 +01:00
Bram Kragten
5a172a64c5 Make entry flow dialog modal (#4440)
* Make entry flow dialog modal

* Add close button

* Update dialog-data-entry-flow.ts

* Fix aria-label
2020-01-10 16:40:19 -08:00
HomeAssistant Azure
433aa16ea6 [ci skip] Translation update 2020-01-11 00:32:34 +00:00
HomeAssistant Azure
50cb8cf3cc [ci skip] Translation update 2020-01-10 00:32:38 +00:00
Sean Mooney
4e5406b27b Typo fix in issue template (#4445)
fixes small typo, necesarry = necessary
2020-01-09 09:29:42 -06:00
Franck Nijhof
80eb80619a Add configuration for Lock Threads on closed pull requests (#4443) 2020-01-09 11:40:25 +01:00
Ian Richardson
bf71b3a869 ♻️ convert ha-attributes to lit-element (#4350)
* ♻️ convert ha-attributes to lit-element

* Address comments

* inline items

* 🐛 Fix attribution display logic
2020-01-09 10:22:23 +01:00
HomeAssistant Azure
ff270c4b7d [ci skip] Translation update 2020-01-09 00:32:44 +00:00
Bram Kragten
8b659498b6 Merge pull request #4439 from home-assistant/dev
20200108.0
2020-01-08 18:54:05 +01:00
David F. Mulcahey
5415068917 Rework the ZHA config panel (#4415)
* convert zha config panel to tabs

* add spacer to prevent combobox from hitting bottom

* break clusters out into their own section

* cleanup buttons

* remove header

* make devices default tab

* convert from tabs to a list view

* convert to table on dashboard

* fix anchor on mobile safari

* cleanup CSS to fix display on mobile

* cleanup card css

* more css cleanup

* fix group page

* remove translations changes

* Update src/panels/config/zha/zha-clusters.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-config-dashboard.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-device-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-groups-dashboard.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* review comments

* fix dangling quote after commit suggestion

* css cleanup

* remove flex rules

* remove flex rules

* css  cleanup

* remove dialog per review comments

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-08 18:35:21 +01:00
Bram Kragten
357a67c00d Bumped version to 20200108.0 2020-01-08 18:26:20 +01:00
HomeAssistant Azure
cbe4269320 [ci skip] Translation update 2020-01-08 17:25:54 +00:00
Bram Kragten
fbd5185ce2 Add ability to remove Lovelace config (#4430)
* Add ability to remove Lovelace config

* Update hc-lovelace.ts
2020-01-08 18:19:10 +01:00
Bram Kragten
a33cf97e2c Fix moving actions with data (#4438) 2020-01-08 18:18:53 +01:00
Pascal Vizeli
7e7da26543 Update azure-pipelines-translation.yml for Azure Pipelines 2020-01-08 16:54:09 +01:00
Bram Kragten
79058e893b Add alert when Google sync failed (#4435) 2020-01-08 15:59:22 +01:00
Bram Kragten
e9231fc17e 20200107.0
20200107.0
2020-01-07 21:26:10 +01:00
Bram Kragten
2eb548bb74 Merge branch 'master' into dev 2020-01-07 20:53:23 +01:00
Bram Kragten
08baf8a757 Bumped version to 20200107.0 2020-01-07 20:50:51 +01:00
Bram Kragten
f02fa6a94b Add multi select to entity registry (#4424)
* Add multi select to entity registry

* Fix filter and sort on status

* Remove unused prop platform

* Review

* Update ha-config-entity-registry.ts
2020-01-07 12:29:42 +01:00
Bram Kragten
2ed6d0e73c Make modal of Lovelace editor dialogs (#4426)
Fixes #4425
2020-01-06 22:25:17 +01:00
David F. Mulcahey
35d9b2ac3c Add the ability to create new Zigbee groups to the ZHA config panel (#4384)
* add group page

* Update src/panels/config/zha/zha-add-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* fix group name handling

* Update src/panels/config/zha/zha-add-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-06 07:02:47 -05:00
Bram Kragten
18d09c6f04 Add UI for restored entities (#4414)
* Add UI for restored entities

* Add conformation for removal

* Apply suggestions

* Guard
2020-01-03 12:44:25 +01:00
Joakim Sørensen
70b81de49d Force rerender on update/save (#4396)
* Force rerender on update/save

* Fix linting issue

* Define properties by using @property() instead

* Add styles to disabled save button

* Change to use @customElement, and remove _generation as a property.
2020-01-02 21:15:26 +01:00
David Cramer
f0808c1f54 Add ha-subppage toolbar css styles (#4409) 2020-01-02 20:55:43 +01:00
Jay
e779f0747e Change TRIGGER to EXECUTE (#4413)
There's been some confusion among new users about what the `TRIGGER` button does in the automation info popup. `EXECUTE` better represents what pressing that button does since it bypasses conditions and simply runs the action like a script. The automation docs at <https://www.home-assistant.io/docs/automation/action/> also say "The action of an automation rule is what is being executed when a rule fires."
2020-01-02 20:16:39 +01:00
David F. Mulcahey
bdd18775c3 Add group editing to the ZHA config panel (#4382)
* add group editing

* Update src/panels/config/zha/zha-devices-data-table.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-devices-data-table.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* review comments

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-02 15:59:18 +01:00
David F. Mulcahey
711d51c022 Disable ZHA device binding buttons when a device to bind isn't selected (#4407)
* only enable buttons when a device is selected

* review comments
2020-01-02 07:24:40 -05:00
David F. Mulcahey
1b0d8bba29 fix area index on ZHA device card (#4406) 2020-01-02 10:50:19 +01:00
Colin Frei
2988cc512f Fix grammatical error (#4403) 2020-01-02 10:28:36 +01:00
Joakim Sørensen
a2f8e5f3e7 Hide protection mode toggle if not usable (#4392) 2020-01-02 10:20:21 +01:00
David F. Mulcahey
680bf06a4b Add group detail view to the ZHA config panel (#4380)
* add group details

* review comments
2019-12-24 10:29:22 -05:00
David F. Mulcahey
ff0b1881e2 Add Zigbee group removal to the ZHA config panel (#4376)
* add remove groups function

* add ability to remove groups

* translations

* review comments

* review comments

* review comments
2019-12-24 08:12:02 -05:00
David F. Mulcahey
de653e1f7b Add Zigbee group viewing to ZHA config panel (#4365)
* add ability to view zigbee groups

* review comments

* remove selectable until used
2019-12-23 10:46:34 -05:00
Bram Kragten
bb41170765 Add language Iban (#4375) 2019-12-23 16:27:41 +01:00
Bram Kragten
0ed2bc93aa Remove uploading translations from Travis (#4374) 2019-12-23 13:39:47 +01:00
Bram Kragten
04770f8ee2 Add language Esperanto (#4373) 2019-12-23 13:39:31 +01:00
Bram Kragten
15a2790b9f Add support to add all device entities to Lovelace (#4356)
* Add support to add all device entities to Lovelace

* Reload config when it was changed while Lovelace was not active

* Localize

* Update ha-panel-lovelace.ts

* Move to device entities card

* Move Lovelace logic to lovelace combine with unused entities

* Unused imports

* Added suggestions and support for YAML mode
2019-12-23 10:39:17 +01:00
Jc2k
83880791b1 Add 'unignore' to DISCOVERY_SOURCES that can be ignored. (#4370) 2019-12-21 17:10:20 +01:00
HomeAssistant Azure
4dca3289f6 [ci skip] Translation update 2019-12-19 16:07:21 +00:00
Pascal Vizeli
083a3ebfc4 Run translation on dev (#4368) 2019-12-19 17:03:05 +01:00
Pascal Vizeli
6117c4e989 Add Auto Translation handling (#4339)
* Add Auto Translation handling

* Cleanup
2019-12-18 16:38:36 +01:00
Bram Kragten
609763e658 Set focus to search when opening add integration dialog (#4357)
* Set focus to search when opening add integration dialog

* Also add to flow form
2019-12-18 16:35:20 +01:00
Bram Kragten
2c57ab60f1 Add ignore discovery button (#4354)
* Add ignore discovery button

* Add seperate list for ignored integrations

* Move translations

* Add zeroconf
2019-12-18 16:22:17 +01:00
Ian Richardson
dd17a153d2 Fire custom LL event (#4361) 2019-12-18 07:40:26 +01:00
Bram Kragten
c2d551bb7c Merge pull request #4341 from bonanitech/patch-2
Upgrade MDI icons to 4.7.95
2019-12-12 17:20:24 +01:00
Mauricio Bonani
e0b1921108 Fix version number 2019-12-09 12:40:11 -05:00
Mauricio Bonani
fcf39ceb96 Upgrade MDI icons to 4.7.95 2019-12-09 12:27:03 -05:00
Mauricio Bonani
3cc979a077 Upgrade MDI icons to 4.7.95 2019-12-09 12:24:36 -05:00
Bram Kragten
9972973774 Merge pull request #4338 from home-assistant/rc
20191204.1
2019-12-09 13:41:33 +01:00
Bram Kragten
20ae32bc26 Bumped version to 20191204.1 2019-12-09 13:03:17 +01:00
Bram Kragten
a29892023b Revert "Add copy entity ID/state/attributes menu button in dev tools/states" (#4337)
* Revert "Add copy entity ID/state/attributes menu button in dev tools/states (#4259)"

This reverts commit 4b56db5255.

* Update package.json
2019-12-09 13:02:41 +01:00
Bram Kragten
b283fec482 Update cloud-google-assistant.ts (#4329) 2019-12-09 13:02:17 +01:00
Bram Kragten
e0116a8236 Fix thingtalk automations creation (#4328) 2019-12-09 13:01:56 +01:00
Bram Kragten
d1990a4bac Revert "Add copy entity ID/state/attributes menu button in dev tools/states" (#4337)
* Revert "Add copy entity ID/state/attributes menu button in dev tools/states (#4259)"

This reverts commit 4b56db5255.

* Update package.json
2019-12-09 12:59:20 +01:00
Bram Kragten
cbba1849e2 Convert script and automation editor to lit (#4327)
* Convert script and automation editor to lit

* Update yarn.lock
2019-12-09 10:59:52 +01:00
Bram Kragten
43393d1647 Update cloud-google-assistant.ts (#4329) 2019-12-09 08:34:36 +01:00
Bram Kragten
b47ee1051c Fix thingtalk automations creation (#4328) 2019-12-07 20:46:04 +01:00
Bram Kragten
393adacc9e Convert automation actions/scripts to Lit (#4324)
* Convert automation actions/scripts to Lit

* Update ha-automation-action-row.ts

* Comments
2019-12-06 12:14:45 +01:00
Bram Kragten
073428849e Convert automation conditions to Lit (#4321)
* Convert automation conditions to Lit

* Split condition editor and row

* Comments

* Update automation.ts

* Update automation.ts
2019-12-05 19:48:06 +01:00
Bram Kragten
e6ac0258e3 Use dynamicElement directive in ha-form (#4317)
* Use dynamicContentDirective

* Turn around

* Remove attributes

* Rename to dynamicElement
2019-12-04 22:58:35 +01:00
Bram Kragten
d7e7798a55 Merge pull request #4318 from home-assistant/dev
20191204.0
2019-12-04 20:02:41 +01:00
Bram Kragten
2557414b11 Merge branch 'master' into dev 2019-12-04 19:30:47 +01:00
Bram Kragten
f7065fbce9 Bumped version to 20191204.0 2019-12-04 19:28:47 +01:00
Bram Kragten
016564eee9 Update translations 2019-12-04 19:22:23 +01:00
Bram Kragten
ff3087c39c Convert automation trigger to litelement (#4315)
* Convert automation trigger to Lit

* Update ha-automation-trigger-row.ts

* dynamicContentDirective

* update

* Lint

* Implement other types
2019-12-04 09:57:47 -08:00
Bram Kragten
239438ee5d Add entity picker to service call action (#4310)
* Add entity picker to service call action

* Use prop instead of attr
2019-12-03 12:30:51 +01:00
Florian Gareis
5458cda31f Add new confim dialog to automation editor (#4255) 2019-12-03 12:21:51 +01:00
Bram Kragten
36f49e66fd Remove empty defaults from time patern trigger automation (#4307) 2019-12-02 11:11:05 -08:00
Bram Kragten
2bafd38ea8 Allow automation actions/scripts to be moved up/down (#4308)
* Allow automation actions/scripts to be moved up/down

* Update index.tsx
2019-12-02 11:10:44 -08:00
Bram Kragten
73b3262491 Fix editing delay action (#4309) 2019-12-02 11:08:38 -08:00
Bram Kragten
808cde033f Update bug_report.md 2019-12-02 17:39:39 +01:00
Bram Kragten
fa8f6b7b91 Add yaml editor to automation actions and scripts (#4306)
* Add yaml editor to automation actions and scripts

* Add types

* Update event.tsx
2019-12-02 14:08:19 +01:00
Bram Kragten
94c120cdb1 Add yaml editor to automation conditions (#4305) 2019-12-02 12:02:35 +01:00
Bram Kragten
7b2be54f8f YAML support for automation triggers (#4289)
* WIP: Add yaml editors to automation

* Fix form overwriting yaml on switching back

* Finish triggers

* prettier
2019-12-02 11:20:09 +01:00
nicop4
4b56db5255 Add copy entity ID/state/attributes menu button in dev tools/states (#4259)
* Added button and js method to copy with copy-to-clipboard library

* Copy entity id working, tooltip added

* copy ok, use ha toast to notify ok

* cleanup code

* add translation

* removed old useless code

* Replaced copy button with menu

* Fix comparison operator & removed commented code

	modifié :         src/panels/developer-tools/state/developer-tools-state.js

* Fix spaces

	modifié :         src/panels/developer-tools/state/developer-tools-state.js

* Improve copy attributes

* only one menu & update translation

* copy attributes in yml format
use paper-icon-item instead of paper-icon-button and add yarn.lock

* removed paper-item
2019-12-02 10:35:49 +01:00
Bram Kragten
93165c9111 Area/multiple devices and name support for thingtalk automations (#4272)
* WIP: Area/multiple devices and name support

* Fix removing devices

* Don't recalc entities for all devices every time

* Use guards

* Update ha-thingtalk-placeholders.ts
2019-12-02 10:30:30 +01:00
Bram Kragten
caa604d5ca Add more aria labels (#4293)
* Add aria labels

* Fix polymer binding
2019-12-02 09:29:02 +01:00
Thomas Lovén
e7e9e2cf85 Allow setting temperature to 0 degrees (#4300) 2019-12-02 09:23:20 +01:00
Bram Kragten
daa04e9973 Fix jumping on iOS when toggle switch (#4275) 2019-11-29 12:41:37 +01:00
Bram Kragten
5355269f5d Check if external app by object (#4280)
* Check if external app by object

* Update core.ts

* Conditional chaining

* add babel optional chaining
2019-11-27 15:44:59 -08:00
Bram Kragten
2665a75250 Don't show hidden scenes (#4285)
* Don't show hidden scenes

* Comments

* computeStateDomain
2019-11-27 15:44:28 -08:00
Bram Kragten
8a39d18323 Bump TypeScript to 3.7 (#4282)
* Bump TypeScript to 3.7

* Update prettier to support ts 3.7

* Prettier

* More prettier

* Even more prettier
2019-11-27 13:51:03 -08:00
Bram Kragten
b8a026397b Don't filter attributes when saving scene (#4278)
* Add cover attributes to scene editor

* Add more

* Remove filtering of attributes

* Update ha-scene-editor.ts
2019-11-27 13:43:46 -08:00
Bram Kragten
bd5fe302eb Revert "Add specific maskable icons (#4283)" (#4284)
This reverts commit de0f1b2b65.
2019-11-27 20:23:58 +01:00
Bram Kragten
de0f1b2b65 Add specific maskable icons (#4283) 2019-11-27 16:43:23 +01:00
Thomas Lovén
defaa2b276 Fix missing semicolons in CSS (#4281)
Introduced in #4269
2019-11-27 13:06:02 +01:00
Bram Kragten
60efe00a1f Fix styling of vaadin elements (#4276) 2019-11-26 16:57:29 +01:00
Davide Varricchio
fe93b993db Change to thermostat card to reflect step_temp on set-temperature (#4221)
* Minor change to thermostat card to reflect step_temp on set-temperature

* Corrected indentation

* Resolved eslint error
2019-11-25 17:42:38 +01:00
Joakim Sørensen
f6afc92d3c Adds "air" at the bottom of the page (#4267)
* Adds "air" at the bottom of the page

* Update src/panels/config/dashboard/ha-config-dashboard.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Add margin to promo
2019-11-25 17:38:08 +01:00
Carlos Gustavo Sarmiento
e4c635c855 Added new CSS property for styling of the app-header component (#4269) 2019-11-25 17:37:33 +01:00
Bram Kragten
a3e59e168f Bumped version to 20191119.6 2019-11-23 21:31:19 +01:00
Thomas Lovén
e56355b406 Bump round-slider version. Fix #4265 (#4266) 2019-11-23 21:30:54 +01:00
Mauricio Bonani
8ef15c50b4 Upgrade MDI icons to 4.6.95 (#4270)
* Upgrade MDI icons to 4.6.95

* Upgrade MDI icons to 4.6.95
2019-11-23 21:26:32 +01:00
Marius
81588469b8 Add secondary-info: last-triggered (#4222)
* Add secondary-info: last-triggered

add last-triggered to the currently available options 'entity-id' and 'last-changed' see:https://www.home-assistant.io/lovelace/entities/#secondary_info

* corrected omission 'attributes'

* added test for attributes.last_triggered

* Update hui-generic-entity-row.ts

* Update hui-generic-entity-row.ts
2019-11-23 21:19:26 +01:00
Joakim Sørensen
70a920af3c Add initial bg color to panels (#4268) 2019-11-23 21:18:54 +01:00
Thomas Lovén
1329e60c89 Bump round-slider version. Fix #4265 (#4266) 2019-11-23 21:12:48 +01:00
Bram Kragten
9b7c095080 Bumped version to 20191119.5 2019-11-21 17:25:50 +01:00
Bram Kragten
654ff99cd1 Bumped version to 20191119.4 2019-11-21 17:04:43 +01:00
Bram Kragten
0511bc360e iOS 9 doesn't support append (#4260) 2019-11-21 17:04:36 +01:00
Bram Kragten
ea9e8cc392 iOS 9 doesn't support append (#4260) 2019-11-21 17:03:35 +01:00
Bram Kragten
8433678371 Bumped version to 20191119.3 2019-11-21 15:22:07 +01:00
Bram Kragten
757bc00854 Fix thermostat card (#4258)
* Fix thermostat card

* Change styling

* Remove margin on mode buttons
2019-11-21 15:21:45 +01:00
Bram Kragten
2551393821 Fix light card (#4257)
* Fix light card

* Remove unused class

* Fix for when entity is not available

* Fix active state
2019-11-21 15:21:20 +01:00
Bram Kragten
0acd41b7f0 Fix thermostat card (#4258)
* Fix thermostat card

* Change styling

* Remove margin on mode buttons
2019-11-21 15:18:16 +01:00
Bram Kragten
85ca73db84 Fix light card (#4257)
* Fix light card

* Remove unused class

* Fix for when entity is not available

* Fix active state
2019-11-21 15:17:55 +01:00
Bram Kragten
444cbd00d9 Update README.md 2019-11-21 15:05:42 +01:00
Bram Kragten
15b500886c Bumped version to 20191119.2 2019-11-20 11:02:01 +01:00
Thomas Lovén
3aac834e72 Version bump round-slider. Fix bad rendering in IE/Edge (#4249) 2019-11-20 11:01:32 +01:00
Thomas Lovén
6edf23b91f Version bump round-slider. Fix bad rendering in IE/Edge (#4249) 2019-11-20 10:55:06 +01:00
Bram Kragten
e445251b02 Bumped version to 20191119.1 2019-11-19 21:08:29 +01:00
Bram Kragten
693151b590 Fix ha-form on edge (#4248) 2019-11-19 21:08:15 +01:00
Bram Kragten
1249c0eea9 Fix ha-form on edge (#4248) 2019-11-19 21:06:52 +01:00
Bram Kragten
3133118870 Update vaadin components (#3571)
* Update vaadin components

* Remove resolution

* Migrate person detail dialog to mwc-dialog

* Fix imports

* Update dialog-person-detail.ts
2019-11-19 11:35:37 -06:00
Bram Kragten
de5c1a0545 Merge pull request #4246 from home-assistant/dev
20191119.0
2019-11-19 13:37:07 +01:00
Bram Kragten
c61e2fb459 Bumped version to 20191119.0 2019-11-19 13:19:33 +01:00
Bram Kragten
64a2a19da3 Update translations 2019-11-19 13:19:27 +01:00
Bram Kragten
74fe1f820c Fix error when no entities (#4244) 2019-11-19 13:16:55 +01:00
Bram Kragten
69929f5dc3 Fix thermostat unavailable (#4245) 2019-11-19 13:16:38 +01:00
Bram Kragten
fcd793fc9e Fix device filtering on mobile (#4243) 2019-11-19 11:41:56 +01:00
Bram Kragten
8a3b1d76a1 Fix removing entity in scene editor (#4241)
Fixes https://github.com/home-assistant/home-assistant-polymer/issues/4237
2019-11-19 00:35:16 +01:00
Thomas Lovén
9f520d7628 Use new scaling features of round-slider (#4172)
* Refresh light card.

* Refresh thermostat card

* Fix paddings

* Fix #4175

* Use action handler

* Address review comments

* Lint

* Padding on percentage

* Remove typo
2019-11-19 00:32:23 +01:00
Bram Kragten
258cfddc3f Remove alias from scene action (#4240)
Fixes https://github.com/home-assistant/home-assistant-polymer/issues/4239
2019-11-19 00:28:35 +01:00
Bram Kragten
3697500402 Fix min value in editor (#4236) 2019-11-19 00:28:08 +01:00
springstan
b4942ad27e Fixed two-part pin code input in Manual Alarm Control Panel (#4213)
* Consolidated code input via clicking GUI pads and using a physical keyboard

* Replaced the querySelector with the query decorator

* Run the query selector once, store its result and reuse it multiple times
2019-11-19 00:27:48 +01:00
Ville Skyttä
1e217e8d2f Support email and url form input types (#4186)
* Support email and url form input types

* Lint

* Lint

* Add types
2019-11-18 18:03:19 +01:00
Bram Kragten
0056237d85 Merge pull request #4233 from home-assistant/dev
20191118.0
2019-11-18 13:08:58 +01:00
Bram Kragten
920ee741f3 Bumped version to 20191118.0 2019-11-18 12:50:11 +01:00
Bram Kragten
6ecc60423f Fix actions not working on touch devices (#4231) 2019-11-18 11:27:44 +01:00
MatthewFlamm
09e7638c89 fix evaluating to false and remove ; (#4228) 2019-11-18 09:00:50 +01:00
Bram Kragten
b82b4a639e Merge pull request #4227 from home-assistant/demo-manifest
Add maskable icon directive to demo manifest
2019-11-18 08:54:07 +01:00
Paulus Schoutsen
d08aa51c16 Add maskable icon directive to demo manifest 2019-11-16 12:09:29 +01:00
Bram Kragten
385ffe6d8f Merge pull request #4226 from home-assistant/dev
20191115.0
2019-11-15 20:21:09 +01:00
Bram Kragten
564e6d4073 Bumped version to 20191115.0 2019-11-15 20:06:35 +01:00
Bram Kragten
a4bd816eb5 Fix localise escaping issue (#4225) 2019-11-15 19:59:47 +01:00
Bram Kragten
13c18a9bb7 Merge pull request #4224 from iantrich/fix-filter-badge
Fix entity-filter-badge update
2019-11-15 19:59:25 +01:00
Ian Richardson
562d7a7cf4 use lastChild 2019-11-15 11:10:47 -06:00
Ian Richardson
89f33a1730 Fix entity-filter-badge update 2019-11-15 09:45:50 -06:00
Bram Kragten
ff7309f5c4 Merge pull request #4218 from home-assistant/dev
20191114.0
2019-11-14 13:47:21 +01:00
Bram Kragten
1c614c855f Bumped version to 20191114.0 2019-11-14 13:28:28 +01:00
Bram Kragten
6a3238951d Updated translations 2019-11-14 13:28:06 +01:00
Bram Kragten
0dab5828fb Add Thingtalk automation generation (#4216)
* thingtalk

* works

* Add device_class support and get placeholders from api

* Update
2019-11-14 13:22:44 +01:00
Joakim Sørensen
d0b9c09f8f Fix person dialog issues (#4210)
* Fix person dialog issues

* Remove repo (should not have been committed...)

* Trigger Travis
2019-11-12 20:30:11 +01:00
Josh McCarty
55f4629256 Wrap the dialog title when using Entity Registry (#4174)
* Wrap the dialog title when using Entity Registry

* Move updated styles to styles()
2019-11-12 14:52:56 +01:00
Joakim Sørensen
004565217e Restructure/reorder configuration dashboard (#4202)
* Reoreder config dashboard

* Changes base translation for config dashboard items to be pleural

* Convert to lit/ts

* remove unneded stuff

* fix child property passes

* Export page configuration

* Apply suggestion

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Change property to CloudStatus
2019-11-10 23:25:13 +01:00
Bram Kragten
c07b39ebde Fix device and entity pickers clear (#4206)
* Fix device and entity pickers clear

* Prevent combobox to open on clear
2019-11-10 13:25:07 -08:00
Bram Kragten
8b17b6ed1c Add attribution and onboarding to voice (#4190)
* Add attribution and onboarding to voice

* Align with backend changes

* Layout + switch to ws for process

* Don't mutate window

* Move speechRecognition

* Add border

* Update ha-voice-command-dialog.ts
2019-11-10 11:30:41 -08:00
Paulus Schoutsen
1d16bdbe54 Merge pull request #4201 from home-assistant/lokalise
Update lokalise cli to version 2 + fix replacing localise vars
2019-11-10 11:30:08 -08:00
Joakim Sørensen
9e2a0c77d5 Adds clear value handler (#4205)
* Adds clear value handler

* Add the same to device picker
2019-11-10 19:23:27 +01:00
Joakim Sørensen
4f41508110 Adds timestamp device_class for sensor (#4204) 2019-11-10 18:41:33 +01:00
Joakim Sørensen
eaedb2e5ae Checked button colors (#4203)
* split switch-checked-color into 2 vars like the unchecked variant

* Apply new css vars

* revert accidental delete

* revert demo changes

* revert property change
2019-11-10 18:40:36 +01:00
Bram Kragten
75ad1f51a9 Update translations_upload_base 2019-11-09 23:49:26 +01:00
Bram Kragten
142175c6ab Update lokalise cli to version 2 + fix replacing localise vars 2019-11-09 23:47:58 +01:00
Joakim Sørensen
f1980d6bcf Fixes history graph card defaults (#4198) 2019-11-09 17:30:08 +01:00
Joakim Sørensen
5a7b5200fe Change correct localization string (#4197)
* Change correct localization string

* Update en.json
2019-11-09 17:29:14 +01:00
Joakim Sørensen
d284d53b93 Reload buttons (#4194)
* Change base translation for ui.panel.config.server_control.section.reloading.core

* Give each btn a new card-action div
2019-11-08 14:39:21 -08:00
Bram Kragten
bc01df42d8 Merge pull request #4193 from home-assistant/dev
20191108.0
2019-11-08 17:41:58 +01:00
Bram Kragten
901752bec3 Update translations (#4192) 2019-11-08 17:12:48 +01:00
Bram Kragten
e3ef3cfae1 Bumped version to 20191108.0 2019-11-08 17:12:28 +01:00
Paulus Schoutsen
ab476d2f1b Add conversation ID to voice dialog (#4189) 2019-11-07 12:21:37 -08:00
Bram Kragten
5ca82fd39c Fix mobile support for voice dialog (#4154)
* Fix mobile support for voice dialog

* Update ha-voice-command-dialog.ts

* typo

* Add extra data functions

* Start listening for choice

* Remove extra data logic
2019-11-04 12:34:59 -08:00
Bram Kragten
da35c263d2 Add scene editor (#4164)
* Add scene editor

* Update ha-config-scene.ts

* Update en.json

* Update ha-scene-editor.ts

* Partial comments

* Types

* 1 more

* Comments

* Lint

* Update ha-device-picker.ts

* Update ha-device-card.ts
2019-11-04 12:07:09 -08:00
Bram Kragten
2a617a9639 Revert "Add Netatmo specific states" (#4171) 2019-11-01 20:35:35 +01:00
springstan
c730aab28f Translated config flow form and steps (#4140)
* Translated config flow form and steps

* Moved translation from config_entry to config_flow

* Renamed translation key from not_all_fields_required to not_all_required_fields
2019-11-01 20:34:10 +01:00
Ian Richardson
274c2016c0 action-handler (#4115)
* action-handler

* comments

* address comments

* finish conversion

* move haptics

* address comments

* lint

* keyup

* double enter

* address comments

* keyup
2019-11-01 13:15:11 -05:00
Bram Kragten
9b3891f778 Fix state-label-badge override name (#4166) 2019-11-01 12:54:14 -05:00
Bram Kragten
b705de956e Fix hassio build (#4167) 2019-11-01 17:42:36 +01:00
Ian Richardson
e37201f84f enter is registering as double_tap (#4161)
* enter is registering as double_tap

* Update long-press-directive.ts

* Update long-press-directive.ts
2019-11-01 16:39:53 +01:00
springstan
f53eea81c4 Translated a bunch of strings (#4137)
* Translated customization page
 - added translation key-value pairs to en.json
 - translated form-customize, customize-icon and its key-value

* Translated mutliple pages:
 - devices
 - entity-registry
 - config-entry
 - dashboard -> hint to advanced mode toggle

* Translated custom panel confirm message

* Added translation for no entries in logbook

* Updated translation keys, tested and removed ha-types file translations

* Removed setting an if to true for testing

* Use template literal instead of string concatenation
2019-11-01 16:12:49 +01:00
springstan
0fa8db1682 Translated unused entities, device registry page and domain toggler dialog (#4141)
* Translated device registry and domain toggler dialog

* Translated unused entities page

* Extracted unused_entities keys and created new section for domain_toggler

* Moved unknow_error and area_picker_label into devices section
2019-11-01 16:07:18 +01:00
Bram Kragten
46f5224e70 Migrate voice command dialog (#4150)
* Migrate voice command dialog

* Cleanup

* Correct types

* Added animation when listening and we should talk back right? :'-)

* Set recognition to english

* Comments

* Update on change of hass
2019-10-29 14:59:35 -07:00
Bram Kragten
12be2a9775 Merge branch 'master' into dev 2019-10-29 15:14:04 +01:00
springstan
6196bbdc5e Translated hui-editor and hui-root pages (#4142) 2019-10-29 13:15:47 +01:00
Marianne Hval
b41f4777d4 More descriptive delete/move buttons + red delete (#4145) 2019-10-29 13:13:30 +01:00
springstan
f2812bc706 Translated automation and script editor (#4146) 2019-10-29 13:12:35 +01:00
Bram Kragten
04500bc237 Merge pull request #4148 from springstan/addTitleAttributeConfigUIMode
Added title attributes and translated them in Configure UI Mode
2019-10-29 13:07:43 +01:00
Bram Kragten
2a6b877cf1 Bumped version to 20191025.1 2019-10-29 12:58:20 +01:00
Bram Kragten
c3896a4613 Only do haptic on entity interactions 2019-10-29 12:56:52 +01:00
Bram Kragten
c6fb896fe4 Merge pull request #4152 from home-assistant/haptics
Only do haptic on entity interactions
2019-10-29 12:53:44 +01:00
Bram Kragten
669fbb7e77 Only do haptic on entity interactions 2019-10-29 12:37:25 +01:00
springstan
971865e4f9 Added title attributes and translated them in Configure UI Mode 2019-10-28 20:13:24 +01:00
Paulus Schoutsen
9078e41855 Clarify tslint disable 2019-10-28 10:45:58 -07:00
Bram Kragten
466c48a7d0 Merge pull request #4128 from Twanislas/netatmo-states-translation
Add Netatmo specific states
2019-10-28 15:51:42 +01:00
Bram Kragten
31a047ce9e Merge pull request #4136 from home-assistant/dev
20191025.0
2019-10-25 17:44:34 +02:00
Bram Kragten
bd24ffa5d0 Bumped version to 20191025.0 2019-10-25 17:12:30 +02:00
Bram Kragten
99f4bd7398 Updated translations 2019-10-25 17:12:22 +02:00
Bram Kragten
417177b097 Merge pull request #4135 from home-assistant/entity-reg-1-col
Change entity reg table to full width
2019-10-25 17:08:43 +02:00
Bram Kragten
c407cab501 Change entity reg table to full width 2019-10-25 16:54:42 +02:00
Bram Kragten
044cf22f47 Fix variable column widths (#4127) 2019-10-24 09:43:28 -07:00
Bram Kragten
75aa940d44 Merge pull request #4129 from home-assistant/delete-view-btn
Fix styling delete view btn
2019-10-24 17:32:04 +02:00
Bram Kragten
7be8080726 Fix styling delete view btn 2019-10-24 12:32:59 +02:00
Antoine Rahier
13fbc813cd Add Netatmo thermostat states 2019-10-24 11:20:06 +02:00
Antoine Rahier
44d1458229 Revert "Add Netatmo specific states"
Wrong file !

This reverts commit f06f3ee2e5.
2019-10-24 11:16:41 +02:00
Antoine Rahier
f06f3ee2e5 Add Netatmo specific states 2019-10-24 11:09:42 +02:00
Bram Kragten
a889a02e15 Merge pull request #4123 from iantrich/aria-ts-translations
translated most aria-labels in ts files
2019-10-24 10:34:46 +02:00
Ian Richardson
6bf3d6a689 lint 2019-10-23 23:20:54 -05:00
Ian Richardson
1d7dcca495 translated most aria-labels in ts files 2019-10-23 23:02:58 -05:00
Bram Kragten
ad8f049570 Merge pull request #4117 from home-assistant/dev
20191023.0
2019-10-23 21:36:21 +02:00
Bram Kragten
73c56a68b6 Bumped version to 20191023.0 2019-10-23 21:16:25 +02:00
Bram Kragten
b34b52f305 Update translations 2019-10-23 21:16:03 +02:00
Ian Richardson
39d052273d support actions on rows (#4023)
* support actions on rows

* address comments

* add type

* pointer events

* move action area to row name to avoid handler competition

* add action to state-badge as well

* correct type

* address comments

* handle 'enter' in long-press and make entities state-badge selecatable
2019-10-23 10:30:09 -07:00
Bram Kragten
e435b9153b Clean up device card (#4108) 2019-10-22 16:11:56 -07:00
Bram Kragten
0792278927 Fix more integrations message (#4106)
* Fix more integrations message

* Period outside link
2019-10-22 16:10:42 -07:00
Josh McCarty
06d59b3cde Adds a white background to the QR code for MFA (#4107) 2019-10-23 00:04:09 +02:00
Josh McCarty
1e7497ad33 Revise hassio update card design (#4041)
* Revise hassio update card design

See https://github.com/home-assistant/home-assistant-polymer/issues/3916

* Remove available text; use secondary text color instead of yellow/orange

* Less bold text for update header
2019-10-22 23:06:18 +02:00
Ian Richardson
49d0f2359b add sensor card to first demo (#4035)
* add sensor card to first demo

* Change to temperature sensor
2019-10-22 14:43:34 -05:00
Timmo
bb73039205 Confirmation Dialog (#4053)
*  Add confirmation dialog

*  Add confirmation dialog to service calls

* 🔨 Change returned value

*  Add confirmation dialog to integration remove

*  Add to entity registry

*  Add to delete card and add translation

*  Add to views deletion

* 🔨 Remove async

* 🔨 Fix min-width for smaller screens

* 🔨 Remove async

* Fix wrong merge

* Update ha-config-entry-page.ts
2019-10-22 21:20:41 +02:00
Bram Kragten
d4d6b7e2ce Fix delete button styling in hui-edit-view (#4102) 2019-10-22 21:17:55 +02:00
Drake Loud
7b5201599d added title to view configuation (#4037)
* added title to view configuation

* replacing let with const

* added localization change

* updated view config title

* linter updates

* Prettier updates
2019-10-22 10:27:36 +02:00
springstan
11c08e9a69 Added view title to heading of Lovelace Add Card picker (#4083)
* Added view title to heading of Lovelace Add Card picker

* Fixed building error, cleaned up code

* Changed _view to _viewConfig and added check for undefined _cardConfig

* Added else if for undefined _cardConfig, added else for manual cards

* Used template literal instead of string concatenation
2019-10-22 10:24:25 +02:00
aquarium
731bb176f7 Fix link to translation documentation (#4098) 2019-10-22 08:43:14 +02:00
Phi Dong
b0fce93de8 Fix issue where long states do not wrap (#4101) 2019-10-22 08:39:37 +02:00
Paulus Schoutsen
fdbe89e87e Fix develop build 2019-10-21 16:51:54 -07:00
Ian Richardson
a8d0a2293f revert lovelace selectable text (#4095) 2019-10-21 16:45:15 -07:00
Paulus Schoutsen
8ac278bc59 Remove unused deps 2019-10-21 16:33:03 -07:00
Paulus Schoutsen
70d6c6b902 Refactor Webpack build scripts (#4093)
* Refactor Webpack build scripts

* Add Gallery too

* Fix icons

* Update travis
2019-10-21 15:02:54 -07:00
Drake Loud
0621218e16 Changed button to text (#4067)
* Changed button to text

* Moved styles to hui-edit-view

* prettier updates
2019-10-21 23:32:15 +02:00
Bram Kragten
2424376fba Change ha-device-picker to combo box + improve name handling + show area (#4089)
* Change ha-device-picker to combo box + improve name handling + show area

* unused import
2019-10-21 12:40:16 -07:00
Bram Kragten
3973374f3f Add positive_time_period_dict to ha-form (#4090)
* Add positive_time_period_dict to ha-form

* select input on focus

* Update ha-form-positive_time_period_dict.ts
2019-10-21 12:36:26 -07:00
Ian Richardson
c25a38b82f add actions to state-badge element and state-label-badge (default Lovelace badge type) (#4028)
* add actions to state-badge element

* address comments

state-label-badge no longer handles clicks
added actions to hui-state-label-badge
moved ha-badges-card to Lit
2019-10-21 14:03:01 -05:00
Ian Richardson
3c0ba1d7eb Convert more-info-sun to Lit (#4075)
* Convert more-info-sun to Lit

* address comments
2019-10-21 13:04:22 -05:00
Ian Richardson
be678b02c5 Convert more-info-weather to LitElement (#4073)
* Convert more-info-weather to LitElement

* address comments

* add shouldUpdate
2019-10-21 13:04:08 -05:00
Ian Richardson
0078b48e3c card level themes (#4057)
* card level themes

weather-forecast
shopping-list
plant-status
markdown
alarm-panel

* fix markdown

* address comments

also added picture cards

* update updated

* address comments

* address comments
2019-10-21 12:38:06 -05:00
springstan
540f1d9bce Fix for Zone icon visibility on Map panel and Lovelace Map card (#4085)
* Set icon color to black in ha-panel-map.js

* Changed icon color depending on dark mode in lovelace map card

* Fixed build error by swapping var for let and const

* Replaced hardcoded style with the light and dark classes
2019-10-21 17:45:40 +02:00
Bram Kragten
5e3cb812ec Fix yaml name collision in card editor (#4079)
* Fix `yaml` name collision

* Add types for js-yaml change to named imports
2019-10-21 17:36:09 +02:00
aquarium
6d10a5dd4c Split data table background color into separate theme variable (#4031)
* Split data table background color into separate theme variable

* Remove reference to MDC in data table theme variable name
2019-10-21 09:27:06 +02:00
Sean Mooney
96d14b7ab7 Changed outdated "GUI editor" reference to "visual editor" (#4068)
Changed warning text from "GUI editor" to "visual editor" to match the updated naming being used in card editors.
2019-10-20 15:10:07 +02:00
Phi Dong
b96b026905 Add hovers (title attrs) to buttons on integrations config entry (#4059)
* Add integration specific titles to config buttons

* Rename tranlation var to integration, move button labels inline
2019-10-20 15:02:11 +02:00
Paulus Schoutsen
c25f2d3941 Move compression from Webpack to Gulp (#4074) 2019-10-20 14:42:20 +02:00
Paulus Schoutsen
785453aa79 Clean up mixins (#4076)
* Clean up mixins

* Lint
2019-10-20 14:31:58 +02:00
Phi Dong
4dbf5327bd Update snapshot actions to be a list (#4045)
* Update snapshot actions to be a list

* Update text of delete button to warning color
2019-10-19 18:03:22 +02:00
springstan
603240c467 Translated developer tools info page (#4054)
* Translated developer tools info page

* Added set or remove as a variable in translation of key default_ui
2019-10-19 17:59:57 +02:00
Paulus Schoutsen
bbc3e7d93f Make updateHass not async (#4060) 2019-10-19 17:18:56 +02:00
Ian Richardson
fbee4937a0 add icon option to entities card name (#4024)
* add icon option to entities card name

* account for only icon

* address comments
2019-10-19 14:25:14 +02:00
Phi Dong
0a77728652 Move Home Assistant and version on info page into h2 (#4061) 2019-10-19 14:24:28 +02:00
Phi Dong
e3ed0cf436 Hide save on card configuration if no card is picked (#4062) 2019-10-19 14:18:59 +02:00
Bram Kragten
d05dc2e4dc Bring back babel (#3974)
* Bring back babel

* bump preset env

* Remove empty TS properties in mixins
2019-10-19 14:06:04 +02:00
Paulus Schoutsen
c437cd3865 Use only decorators (#4058)
* Use only decorators

* Remove unused imports
2019-10-18 22:50:27 -07:00
springstan
442171169b Translated developer tools template page (#4050) 2019-10-18 23:05:07 +02:00
springstan
cc12dbb6ee Translated developer tools services page (#4049)
* Translated developer tools services page

* Changed translation parameter from error to data
2019-10-18 23:04:43 +02:00
springstan
60b3a960ae Translated developer tools mqtt page (#4052) 2019-10-18 23:04:26 +02:00
shbatm
5a957c3c9e Add width property to light card to fix #3964 (#3972)
* Add width property to light card to fix #3964

* Update Light Card Name CSS

* Update css per review
2019-10-18 22:58:25 +02:00
Ian Richardson
be4d431dc3 more-info dialog for counter (#4038)
* more-info dialog for counter

* address comments
2019-10-18 14:55:53 -05:00
Ian Richardson
0005c75091 fix double tap on glance entity (#4051) 2019-10-18 21:08:12 +02:00
springstan
880b382a16 Translated developer tools logs page (#4046)
* Translated developer tools logs page

* Changed translation key from load_log to load_full_log
2019-10-18 17:43:04 +02:00
Ian Richardson
d012512a79 add feature request template (#4026) 2019-10-18 17:01:41 +02:00
springstan
e2ac842690 Translated developer tools state page (#4039)
* Translated developer tools state page

* Fixed merge error by updating used icon in developer-tools-state.js
2019-10-18 16:33:11 +02:00
Ian Richardson
67d8d48855 translate view editor and explain panel mode (#4043) 2019-10-18 16:28:31 +02:00
Bram Kragten
00f2d36cb5 Migrate ha-form to lit (#4000)
* Migrate ha-form to lit

* Fix import path

* Update

* add default, change suffix, fix import

* Fix select
2019-10-18 16:08:34 +02:00
springstan
035057b185 Made thermostat history graph localizable (#4004)
* Made thermostat history graph localizable

* Changed string concatenation with white spaces from .join() to template literal

* Used variables in translations instead of template literals

* Update src/translations/en.json

Removed unnecessary space in heating value

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>
2019-10-17 23:28:12 +02:00
springstan
982966c8d9 Translated developer tools events page (#4033)
* Translated developer tools events page

* Implemented translations (alert on empty event type, notification on firing an event), Removed unnecessary translation key-value pairs

* Added falsely removed key-value pairs
2019-10-17 23:17:39 +02:00
Ian Richardson
f5e3a9ad40 Convert thermostat to round-slider (#3734)
* Convert to round-slider

Closes https://github.com/home-assistant/home-assistant-polymer/issues/3622
Closes https://github.com/home-assistant/home-assistant-polymer/issues/2756

* scaling

* address review comments

* css tweaks

* remove jquery

* address comments

* simplify set-temperature

* handle long name

* remove increased handleSize

* address comments

* address comments

* address comments

* address comment

* need coffee
2019-10-17 21:00:39 +02:00
Ian Richardson
141c3f1ea4 add confirmation option to actions (#4006)
* add confirmation option to actions

* address comments
2019-10-16 11:03:36 -05:00
Ian Richardson
4ea483e3de Switch theme (#4017)
* introduce ha-switch theming

* fix typo

* remove comment
2019-10-16 17:08:28 +02:00
Josh McCarty
8eca956cd1 Uses information-outline icon for entity more info link (#4016) 2019-10-16 16:53:25 +02:00
Ian Richardson
c9242a5075 Custom badges (#3867)
* custom badges

* incremental

* functional

* cleanup

* cleanup

* address review comments

* address more comments

* address review comments

* address review comments

* cleanup

* address review comments

* address comments

* address comments

* fix entity-filter

* set hass once

* hass
2019-10-16 16:09:13 +02:00
Ian Richardson
df29a5becb Add double tap action (#3879)
* add dbltap_action

* apply to picture-glance

* types and boolean

* fix typo

* simplify double tap logic

* extract hasDoubleClick functionality

* address comments

* address comments

* double_tap_action
2019-10-16 08:57:05 -05:00
Ian Richardson
fb589337f8 display card errors with yaml config (#4018) 2019-10-16 09:22:57 +02:00
Ian Richardson
ea5ee6189d filter system users from Person editor (#3958)
* filter system users from Person editor

* address review comments

* address comments

* address comments
2019-10-15 19:19:46 -05:00
Josh McCarty
a39e47cced Updates alarm more-info dialog button styles to match lovelace card (#3955)
* Updates alarm more-info dialog button styles to match lovelace card

* Use flex for controlling button size

* Limit max-width of alarm buttons

This matches the width of the code input field
2019-10-15 21:04:29 +02:00
Ian Richardson
49d69f65ad add show_state option for picture-glance entities (#3937)
* add states option below picture-glance entities

* address review comments
2019-10-15 09:49:03 +02:00
Marcus Fredlund
424d677bcb Alarm Panel card: number buttons outline and adjusted padding and font-size fixing #2812 (#3991)
* outline on alarm panel buttons

* Fix for font-size before mwc-button is working.

* suggested edits from review

* changed to just outline

* Remove base-unit
2019-10-14 21:29:37 +02:00
springstan
59e4cdc62a Added integration name to System Options dialog (#3996)
* Added integration name to System Options dialog

* Passed integration name into translation string

* Translated integration name

* Added option for no translation
2019-10-14 21:28:44 +02:00
Bram Kragten
9d3dfad98c Update integration-badge.ts (#4001) 2019-10-14 21:28:13 +02:00
Erik Montnemery
555b746f4b Allow device actions to specify extra fields (#4002)
* Allow device actions to specify extra fields

* Typing etc.

* Use deviceAutomationsEqual to compare automations
2019-10-14 21:27:51 +02:00
Bram Kragten
ce6a97d065 Merge pull request #3995 from scop/customize-yaml-message
Improve customize.yaml non-inclusion warning message
2019-10-14 21:27:07 +02:00
Ville Skyttä
88567df36d Prettier 2019-10-14 22:03:28 +03:00
Bram Kragten
f55cbd9e9a Merge pull request #3999 from home-assistant/dev
20191014.0
2019-10-14 11:01:07 +02:00
Bram Kragten
7d00cc1eff Merge branch 'master' into dev 2019-10-14 10:28:17 +02:00
Bram Kragten
29301ddee7 Bumped version to 20191014.0 2019-10-14 10:22:13 +02:00
Bram Kragten
978b773968 Update translations 2019-10-14 10:21:56 +02:00
springstan
4f30cae6aa Translated Advanced mode toggle (#3997) 2019-10-13 22:39:08 +02:00
Ville Skyttä
5f29b66a8d Further tweak the message 2019-10-13 21:15:06 +03:00
springstan
b94da1bd19 Added note about more integrations to bottom of Set up new integration dialog (#3992)
* Added note about more integrations to bottom of Set up new integration dialog

* Fixed note about more integration by encapsulating it in one paragraph
2019-10-13 15:57:24 +02:00
Franck Nijhof
f9b0a0fc13 Fixes documentation URL for HTML5 push notifications (#3993) 2019-10-13 13:34:34 +02:00
Sven
300ffdae04 Add no devices available hint to person editor (#3906)
* Add no devices available hint to person editor (#3731)

* Fix device_tracker check and adjust messages

* fix lint

* Add location, fix memoize usage, hide entity picker and update text

* Move links to a list
2019-10-13 13:22:37 +02:00
Tom Raithel
476525e0d4 Remove 'Pick script to edit' headline from script editor (#3873)
Fixes #3829
2019-10-13 13:18:43 +02:00
Ville Skyttä
edecf9d58f Improve customize.yaml non-inclusion warning message
Link to relevant docs, note that changes won't be applied after a
restart if the include isn't in place (changes _do_ actually affect the
current runtime configuration and are persisted in customize.yaml).
2019-10-13 14:07:02 +03:00
Sean Mooney
38bf2e116b Update ISSUE_TEMPLATE.md (#3987)
Now that ui-schema repo is merged here and there's a feature request label, I think this line is no longer necessary.

 `- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests`
2019-10-12 23:14:51 -07:00
Paulus Schoutsen
0719c4d1ae Add cache headers to demo and cast (#3982)
* Add cache headers to demo and cast

* Demo only to copy static from frontend public folder

* Update headers

* Update demo headers

* Update cast headers

* Update _headers

* Update _headers
2019-10-12 23:12:01 -07:00
Bram Kragten
12840231be Add code mirror editors to the dev-tools data fields (#3981)
* Add yaml code mirror editor to the dev-tools yaml fields

* Add jinja2 editor on dev template

* Migrate to UpdatingElement, review comments

* update cm, add types

* types

* dev tools mqtt
2019-10-12 12:33:51 -07:00
Bram Kragten
4728c12225 Update google cloud card (#3978)
* Update google cloud card

* Oops
2019-10-11 08:48:56 -07:00
Bram Kragten
90526ac563 Migrate entity registry to data-table (#3965)
* Migrate entity registry to data-table

* icons

* Styling

* Review comments

* fix not selector

* typos + move columns out of class

* Localize + comments

* Fucked up the rebase
2019-10-11 14:55:45 +02:00
Hayley McIldoon
6f7ea03e35 Remove redundant text in Cloud panel #3824 (#3967) 2019-10-11 14:55:12 +02:00
Erik Montnemery
78900e05ad Allow device conditions to specify extra fields (#3973) 2019-10-11 13:16:54 +02:00
Ian Richardson
495f4aa19c ability to hide tabs (#3811)
* ability to hide tabs

* address review comments

* address review comments

* address review comments

* review comments
2019-10-11 13:14:34 +02:00
Bram Kragten
88c480759f Convert YAMLTextArea to code mirror editor (#3980)
* Convert YAMLTextArea to code mirror editor

* Review comments

* Clean up
2019-10-10 21:58:21 +02:00
Josh McCarty
ab75365636 Move alexa state reporting switch position for consistency (#3953) 2019-10-10 10:51:58 +02:00
Tom Raithel
0266617c71 Remove overflow attribute from view column (#3971)
Fixes #3253
2019-10-10 10:50:46 +02:00
Mauricio Bonani
aef45c5043 Upgrade MDI icons to 4.5.95 (#3977)
* Upgrade MDI icons to 4.5.95

* Upgrade MDI icons to 4.5.95
2019-10-10 09:44:10 +02:00
Hayley McIldoon
deeb0146c7 Renamed Toggle Editor button #3617 (#3968) 2019-10-09 23:03:46 +02:00
Bram Kragten
5dea674f20 Merge pull request #3975 from home-assistant/rc
20191002.2
2019-10-09 22:19:34 +02:00
Bram Kragten
646fe34d09 Bumped version to 20191002.2 2019-10-09 21:57:58 +02:00
Ian Richardson
c67907aa58 simplify 2019-10-09 21:54:58 +02:00
Ian Richardson
e78f4c5ace picture-glance: check for entity state change 2019-10-09 21:54:34 +02:00
Bram Kragten
e891fdc3eb Save orig value + some cleanup 2019-10-09 21:53:48 +02:00
Erik
95a258c2a5 Allow device triggers to specify extra fields 2019-10-09 21:53:30 +02:00
Bram Kragten
f1fabd09a6 Merge pull request #3970 from emontnemery/automation_device_action_form
Allow device triggers to specify extra fields
2019-10-09 21:48:57 +02:00
Bram Kragten
0d77bdaf32 Merge pull request #3949 from iantrich/fix-glance-state_image
picture-glance: check for entity state change
2019-10-09 21:48:05 +02:00
Bram Kragten
320be2e5d9 Convert integration entry page to data table (#3963)
* Convert integration entry page to data table

* Simplify device-card

In a future PR this has to be changed further

* Center no devices text

* Review comments
2019-10-09 08:48:41 -07:00
Bram Kragten
6a098ad0b5 Save orig value + some cleanup 2019-10-09 13:34:34 +02:00
Erik
e895e91a11 Allow device triggers to specify extra fields 2019-10-09 07:41:25 +02:00
Paulus Schoutsen
fc3f7ca4b2 Mark Persian as right to left. 2019-10-08 10:24:21 -07:00
Bram Kragten
1f09d848c5 Merge pull request #3961 from home-assistant/rc
20191002.1
2019-10-08 17:55:43 +02:00
Bram Kragten
4d794f6088 Hide empty blocks on device page (#3950)
* Hide empty blocks on device page

* lint

* Rename entities on device rename

* check if entity_id is valid

* clarify var name

* Review comments

* Use regex to replace not allowed chars

* Align with backend
2019-10-08 17:54:17 +02:00
Bram Kragten
9ad7f0dbac Hide empty blocks on device page (#3950)
* Hide empty blocks on device page

* lint

* Rename entities on device rename

* check if entity_id is valid

* clarify var name

* Review comments

* Use regex to replace not allowed chars

* Align with backend
2019-10-08 08:53:31 -07:00
Bram Kragten
9f39610153 Update translations 2019-10-08 16:13:52 +02:00
Bram Kragten
12d8a04c15 Update translations 2019-10-08 16:13:06 +02:00
Bram Kragten
73b0f5949e Fix updating entity id in entity-registry-dialog (#3962) 2019-10-08 12:28:49 +02:00
Bram Kragten
0f7a3887a7 Fix updating entity id in entity-registry-dialog (#3962) 2019-10-08 12:27:29 +02:00
Bram Kragten
ac75ce038a Bumped version to 20191002.1 2019-10-08 10:33:16 +02:00
Bram Kragten
8de9a73741 Add report state toggle for Google (#3855) 2019-10-08 10:28:37 +02:00
Bram Kragten
ef51f29e28 Add report state toggle for Google (#3855) 2019-10-08 10:25:57 +02:00
Ian Richardson
b61bbee35a fix media row secondary info (#3957) 2019-10-08 10:10:37 +02:00
Markus Nigbur
64dd8c463d Changed the states-ui toggle button to unobstrusive text link. (#3959) 2019-10-08 10:01:51 +02:00
akargl
d2a95e9f06 Add default text to Markdown card editor (#3960) 2019-10-08 09:58:37 +02:00
Timmo
0cb0525516 Add localized names to card editor (#3941)
* 🔨 Add localized names to card editor

* ✏️ Rename alarm_panel to alarm-panel

* ✏️ Proper case

* 🔨 Move to else

* 🔨 Remove name and object from card picker
2019-10-07 20:17:12 +02:00
Ian Richardson
dcaf4fdfe2 add title option to stack cards (#3839) 2019-10-07 10:17:52 +02:00
ottersen
0c13757910 Correct usage of Zigbee vs ZigBee #1 (#3938)
ref clarification by Zigbee Alliance of correct usage of "Zigbee" vs "ZigBee"

https://twitter.com/Frenck/status/1178239254713061377
2019-10-07 09:55:31 +02:00
ottersen
0cdcd74c9d Correct usage of Zigbee vs ZigBee #2 (#3939)
ref clarification by Zigbee Alliance of correct usage of "Zigbee" vs "ZigBee"

https://twitter.com/Frenck/status/1178239254713061377
2019-10-07 09:55:18 +02:00
Phi Dong
db3968399f Add intro to OZW log z-wave config section (#3935) 2019-10-07 09:53:37 +02:00
Malte Franken
7494a49238 Normalize longitude to the range between -180 to +180 degrees (#3872)
* normalize longitude to the range between -180 to +180 degrees

* only normalize longitude if out of valid range
2019-10-07 09:49:00 +02:00
Ian Richardson
55d2a3c8b1 simplify 2019-10-06 23:16:14 -05:00
Ian Richardson
be4e45c22c picture-glance: check for entity state change 2019-10-06 23:08:14 -05:00
Matthew Donoughe
efb28d337a editor support for scene script action (#3942)
Script support added in home-assistant/home-assistant#27223
2019-10-05 15:14:37 -07:00
Phi Dong
edd77e1f32 Exclude persistent_notifications domain when computing unused entities (#3936) 2019-10-05 14:01:00 +02:00
Bram Kragten
848dd7e071 Fixes to script localization (#3930)
* Fixes

* fix toast
2019-10-04 21:46:32 +02:00
Bram Kragten
59d4a4247a Fix whitespace error in services (#3880) 2019-10-04 17:21:43 +02:00
Jens Vanhooydonck
ba79633758 HA Cloud overflow wrap (#3905)
* Overflow of Remote Control URL

* Overflow wrap of certificate information

Closes #2982

* Changed styling to class break-word

* Changed break-work to class

* Update src/panels/config/cloud/account/cloud-remote-pref.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>
2019-10-04 16:46:01 +02:00
Charles Garwood
860973bdbd Add localization to ZHA panel (#3881)
* Add localization for ZHA config panel

* Additional ZHA panel localization
2019-10-04 14:07:49 +02:00
Sven
d4d897e79e Make cloud account panel translatable (#3875)
* Add localization to cloud login & register (#3844)

* Add localization to cloud account (#3844)

* Add localization to cloud forgot password (#3844)

* fix lint errors

* Update src/panels/config/cloud/account/cloud-account.js

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/cloud/register/cloud-register.js

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* fix issues pointed out in review

* fix code style issue

* Add localization for dialogs, alexa and google

* fix exposed entities name
2019-10-04 13:14:53 +02:00
Bram Kragten
4850f3d588 Fix whitespace error in services (#3880) 2019-10-04 12:35:49 +02:00
Bram Kragten
8bc53c235f Fix styling system options (#3877) 2019-10-04 12:04:27 +02:00
Charles Garwood
c74793b1d5 Add link to Z-Wave panel docs (#3874) 2019-10-03 20:51:37 +02:00
Marcel Brückner
56bac8a8c1 Make script editor translatable (#3866)
* Make script editor's script picker translatable (home-assistant/home-assistant-polymer#3848)

* Make script editor translatable (home-assistant/home-assistant-polymer#3848)

* Fix linting errors (home-assistant/home-assistant-polymer#3866)

* Fix linting errors (home-assistant/home-assistant-polymer#3866)

* Move unsaved_confirm translation key to common section (home-assistant/home-assistant-polymer#3866)

Instead of adding the same text multiple times for every section, add a common section to indicate reusable translations.

* Add variable to localization text

* Use JavaScript instead of Polymer data binding
2019-10-03 20:31:53 +02:00
Sven
184575fd54 Add localization to persons config page (#3846) (#3871) 2019-10-03 14:38:34 +02:00
Sven
e148559d3e Add localization to user config page (#3845) (#3869) 2019-10-03 12:22:45 +02:00
Phi Dong
95b76dbb85 Fix issue where help icon overlapped with header text on mobile devices (#3868) 2019-10-03 11:50:36 +02:00
Bram Kragten
496bb9dc39 Merge pull request #3864 from home-assistant/dev
20191002.0
2019-10-02 22:46:27 +02:00
Bram Kragten
351ba3e701 Merge branch 'master' into dev 2019-10-02 22:31:00 +02:00
Bram Kragten
260f428bc6 Bumped version to 20191002.0 2019-10-02 22:25:49 +02:00
Bram Kragten
3622514131 Fix dark switch for map card editor (#3856) 2019-10-02 21:26:48 +02:00
Bram Kragten
391b2dcf6a Align background with tiles (#3858) 2019-10-02 21:25:59 +02:00
Bram Kragten
a02bf1fd48 Allow yaml in dev states (#3859)
* Allow yaml in dev states

* remove json
2019-10-02 21:25:38 +02:00
Bram Kragten
4cf9472bf4 Allow yaml in dev services (#3860)
* Allow yaml in dev services

* remove json
2019-10-02 21:23:37 +02:00
Bram Kragten
74d1de7313 Allow yaml in dev events (#3861)
* Allow yaml in dev events

* remove json
2019-10-02 21:22:06 +02:00
Bram Kragten
cd6fd6a46c Allow yaml in script and automation (#3862)
* Allow yaml in script and automation

* Restore defaults

* Rename class

* Catch errors in constructor

* Update yaml_textarea.tsx
2019-10-02 21:21:13 +02:00
Bram Kragten
a6dda90b13 Default hide disabled entities from entity registry (#3863)
* Default hide disabled entities from entity registry

* localize

* Use memoize
2019-10-02 21:20:47 +02:00
Bram Kragten
7add8a2ea0 Guard for null badges (#3841)
* Guard for null badges

* Check for null badges on load and save
2019-10-02 09:29:36 -07:00
Bram Kragten
b927a3ef29 Add entity registry functions to device page (#3843)
* Add entity registry functions to device page

* Move update/remove logic to dialog

* Hide disabled entities by default
2019-10-02 09:23:42 -07:00
Ian Richardson
76d3218130 Allow user selection of text in Lovelace (#3605)
* Allow user selection of text in Lovelace

Closes https://github.com/home-assistant/home-assistant-polymer/issues/2110

* set negative tabindex

This appears to work locally, but pushing it up to try on the demo to be sure

* toggle class for android

* apply suggestions
2019-10-02 13:33:16 +02:00
Franck Nijhof
8b6d8f9086 Update documentation link URL for integrations (#3857) 2019-10-02 13:23:41 +02:00
Bram Kragten
ffaecb29b7 Update translations 2019-10-02 12:58:11 +02:00
Ian Richardson
fa74295c0b add actions to picture-glance entities (#3807)
* add actions to picture-glance

* remove unnecessary entity property

* Update hui-picture-glance-card.ts
2019-10-02 11:22:33 +02:00
Bram Kragten
ea50d486da Fix some styling of ha-switch (#3842)
If content is slotted it will get some margin to the toggle like the old switch.
2019-10-01 11:17:10 -07:00
Bram Kragten
3cf4b890b6 Remove local mdc-datatable (#3840)
With version 3.2.0 of mdc-datatable they removed the ts files from the published package, so we can now use it again.
2019-10-01 11:34:25 +02:00
Sean Mooney
313b984a53 Add missing domain icons (#3836)
`persistent_notification` and `zone` were both missing domain icons.
2019-10-01 10:11:44 +02:00
Bram Kragten
7d09e29d60 Use filter worker in data-table (#3808)
* Filter worker

Doesn't work

* Remove template for worker

* Move files

* Sort to worker + debounce filter

* Improve performance

* Update ha-data-table.ts
2019-09-30 15:53:48 -07:00
Ian Richardson
7e979f0cf1 add image option to entities card (#3832) 2019-09-30 22:25:09 +02:00
Ian Richardson
64366dc99a fix alignment of switches positioned absolutely (#3812)
* fix alignment of switches positioned absolutely

* wrap switches

* remove !important
2019-09-30 21:27:19 +02:00
Ian Richardson
c69585db98 add state_filter to picture cards (#3791) 2019-09-30 09:49:11 +02:00
Bram Kragten
2dd5cd586b Add a setting for vibration (#3813)
* Add a setting for vibration

* Update en.json
2019-09-27 10:23:30 +02:00
Bram Kragten
05a258c886 Add hassio supervisor and os update notice (#3798)
* Add hassio supervisor and os update notice

* Cleanup

* Update hassio-dashboard.ts

* Fix styling

* Check if system has HassOs

* Remove unused import
2019-09-26 13:47:34 +02:00
Bram Kragten
f4bd42dfd4 Add name to picture entity struct (#3809) 2019-09-25 23:37:54 +02:00
Bram Kragten
41e5e7c1ae Update yarn.lock 2019-09-25 22:41:17 +02:00
Ian Richardson
95dfcafce3 UI Editor for picture-glance card (#3709)
* UI Editor for picture-glance card

Closes https://github.com/home-assistant/home-assistant-polymer/issues/3700

* address review comments

* address review comments

* fallback to yaml if state image used

* push default changes

* default image for config

* Update hui-picture-glance-card-editor.ts
2019-09-25 15:38:26 -05:00
Bram Kragten
111d1afc21 Fix firefox overflow (#3806)
* Fix firefox overflow

MIght fix...

* Add comment

* Align
2019-09-25 21:52:21 +02:00
Ian Richardson
886c6dd88c Expand entity-filter options (#3692)
* Expand entity-filter options

Closes https://github.com/home-assistant/home-assistant-polymer/issues/3543

Adds the following to `state_filter`:
- `operator` option. Specify operator to use in comparison.
- `value` option. Specify value to compare against.
- `attribute` option.  Specify attribute to use instead of state in comparison.

e.g.
```yaml
type: entity-filter
state_filter:
  - "on"
  - operator: ">"
    value: 90
entities:
  - sensor.water_leak
  - sensor.outside_temp
  - entity: sensor.humidity_and_temp
    state_filter:
      operator: ">"
      value: 50
      attribute: humidity
```

* Update evaluate-filter.ts

* verify state_filter exists on card or each entity

* address review comments

* apply Bram's suggestion

* address review comments

* cleanup
2019-09-24 10:34:57 +02:00
On Freund
38b817bd67 Green shade in climate history for platforms that don't support hvac_action (#3787)
* Green shade in climate history for platforms that don't support HVAC_ACTION

* The presence of hvac_action can change over time

* Move static condition out of anon function
2019-09-24 10:34:13 +02:00
Ian Richardson
c59b6626f2 UI editor for history-graph (#3782)
* UI editor for history-graph

* add translations
2019-09-24 10:00:52 +02:00
Ian Richardson
2cc196e3fb cleanup editors (#3786)
* cleanup editors

* address review comments
2019-09-23 17:11:45 -05:00
Erik Montnemery
a08884fed6 Add support for and and or conditions to the automation editor. (#3724)
* Add support for `and` and `or` conditions in automations

* Remove debug prints, fix lint errors

* Fix

* Merge `and` and `or` conditions

* Add missing file
2019-09-23 14:07:57 -07:00
Bram Kragten
2fe4a02b6b Add support for panels to cast (#3796)
* Add panel support to cast

* Set background
2019-09-23 14:03:45 -07:00
Paulus Schoutsen
7c793c1cdb Bumped version to 20190919.1 2019-09-23 13:17:48 -07:00
Bram Kragten
a0b848acc4 Bring back babel for ES5 builds (#3797)
* Bring back babel for ES5 builds

* Remove ts from babel
2019-09-23 13:17:44 -07:00
Bram Kragten
a1b9a092d0 Bring back babel for ES5 builds (#3797)
* Bring back babel for ES5 builds

* Remove ts from babel
2019-09-23 13:15:12 -07:00
Bram Kragten
993d390ea5 Add device automation options to device page (#3776)
* Add device automation options to device page

* Update

* Fill automation editor with data

* Update ha-automation-editor.ts

* Remove dupe deps

* Fix imports
2019-09-23 14:13:44 +02:00
Ian Richardson
1f4d359050 Replace all default exports in common/ with named exports (#3790) 2019-09-23 10:57:47 +02:00
Ian Richardson
f871387fa6 new action: url (#3773)
* new action: url

Takes a `url_path` option.
Closes https://github.com/home-assistant/ui-schema/issues/249
I'm experience the issue described here with my string values in the action-editor: https://github.com/home-assistant/home-assistant-polymer/issues/2645. Have not been able to track down where the issue is.

* Fix losing config on init

* fix action-editor
2019-09-22 21:56:29 +02:00
Ian Richardson
9a92ed31f6 picture entity editor fix (#3783)
* forgot to push commit

* resolve broken build
2019-09-21 22:42:46 -05:00
Ian Richardson
37129adfab UI Editor for picture-entity card (#3708)
* UI Editor for picture-entity card

Closes https://github.com/home-assistant/home-assistant-polymer/issues/3699

* address review comments

* translations
2019-09-21 22:15:30 -05:00
Paulus Schoutsen
ec52e71c71 Upgrade some deps (#3780)
* Upgrade TS

* Remove unused uglifyjs webpack plugin

* Upgrade some webpack stuff
2019-09-21 11:59:51 +02:00
Ian Richardson
5e28e1b320 Switch paper-toggle-button to mwc-switch (#3683)
* Switch paper-toggle-button to mwc-switch

Closes https://github.com/home-assistant/home-assistant-polymer/issues/3352

* ha-switch

* apply not-checked theming

Thanks @bramkragten!

* address review comments
2019-09-21 11:59:27 +02:00
Ian Richardson
9a7eb3d406 light-card: icon option (#3771)
* light-card: icon option

closes https://github.com/home-assistant/home-assistant-polymer/issues/3768

* add theme select label property

* address review comments
2019-09-20 16:10:14 -05:00
Erik Montnemery
eee0c2e53f Add support for automation description (#3723)
* Add support for automation annotation

* Update label and add placeholder

* Rename annotation to description

* Adress review comments, fix lint errors
2019-09-20 08:19:38 -07:00
Bram Kragten
145259e82f Add Language Urdu (#3750)
* Add Urdu

* Changed name to native name
2019-09-19 16:27:54 -07:00
Bram Kragten
d0cc4c2715 Move views to own folder + use update vs updated in panel view (#3761)
* Move views to own folder + use update vs updated in panel view

* updated -> update
2019-09-19 09:10:02 -07:00
Paulus Schoutsen
4d97a47e08 Merge pull request #3770 from home-assistant/dev
20190919.0
2019-09-19 09:01:35 -07:00
Paulus Schoutsen
4ef5a8da70 Bumped version to 20190919.0 2019-09-19 08:59:57 -07:00
Paulus Schoutsen
cdfd0afdf4 Update translations 2019-09-19 08:59:23 -07:00
Bram Kragten
d250a931e6 fix continue integration flow (#3766)
* fix continue integration flow

* Remove test button
2019-09-19 08:57:17 -07:00
Bram Kragten
cd2b92a449 Show toast on success save entity settings (#3763) 2019-09-19 08:53:16 -07:00
Bram Kragten
c617cb5b12 Align behaviour state badge (#3767) 2019-09-19 08:53:00 -07:00
Paulus Schoutsen
e7ac95e314 Merge pull request #3757 from home-assistant/dev
20190918.1
2019-09-18 13:02:58 -07:00
Paulus Schoutsen
7bab9cb464 Remove unused templates attributes from demo 2019-09-18 13:02:45 -07:00
Paulus Schoutsen
bb5ab958c1 Bumped version to 20190918.1 2019-09-18 12:52:25 -07:00
Paulus Schoutsen
2d92ffaa4d Update translations 2019-09-18 12:52:20 -07:00
Mauricio Bonani
7a7a0f772a Upgrade MDI icons to 4.4.95 (#3755)
* Upgrade MDI icons to 4.4.95

* Upgrade MDI icons to 4.4.95
2019-09-18 12:38:45 -07:00
Paulus Schoutsen
c898db5010 Fixes to device page (#3756)
* Fixes to device page

* Cache page with data page
2019-09-18 12:38:27 -07:00
Bram Kragten
b6fbf4da3a Cast: Show error message instead of number (#3752)
* Cast: Show error message instead of number

* Update hc-main.ts
2019-09-18 09:33:58 -07:00
Bram Kragten
0bfc61629e Fix save button more info settings (#3751)
Would be blue on blue on mobile
2019-09-18 09:23:09 -07:00
Ian Richardson
e594fcfc42 lower unavailable font-size (#3746)
Closes https://github.com/home-assistant/home-assistant-polymer/issues/3745
2019-09-18 17:19:42 +02:00
Bram Kragten
84ed6d8fb3 Merge pull request #3748 from home-assistant/dev
20190918.0
2019-09-18 14:22:53 +02:00
Bram Kragten
31ae115062 Fix set theme on panel (#3749) 2019-09-18 14:12:54 +02:00
Bram Kragten
fca885a17a Bumped version to 20190918.0 2019-09-18 13:56:01 +02:00
Bram Kragten
29dff42de4 Make panel-view it's own component (#3747)
* Make panel-view it's own component

* Convert to updatingelement
2019-09-18 13:54:21 +02:00
Bram Kragten
f5b3a82922 Merge pull request #3742 from home-assistant/dev
20190917.2
2019-09-17 21:13:48 +02:00
Bram Kragten
54beaad7e5 Bumped version to 20190917.2 2019-09-17 21:00:13 +02:00
Bram Kragten
e30f9d4a66 Add device config page (#3695)
* Add device config page

* Remove unused imports

* Revert a lot

Make the PR smaller

* Change columns add battery level

* memoize

* don't bubble

* Add mobile view

* fix filter mobile

* Comments
2019-09-17 20:59:43 +02:00
Bram Kragten
27264b27a9 Fix (#3741) 2019-09-17 09:50:51 -07:00
Bram Kragten
2b1f9460a8 Fix styling mwc-button (#3740) 2019-09-17 09:06:53 -07:00
Bram Kragten
4641cd65ca Merge pull request #3739 from home-assistant/dev
20190917.1
2019-09-17 14:18:45 +02:00
Bram Kragten
1f6fe5dfcf Bumped version to 20190917.1 2019-09-17 14:12:46 +02:00
Bram Kragten
058b4ba658 Fix theme rgb creation (#3738) 2019-09-17 14:08:48 +02:00
849 changed files with 116027 additions and 69680 deletions

View File

@@ -1,37 +0,0 @@
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Do not delete any text from this template!
-->
**Home Assistant release with the issue:**
<!--
- Frontend -> Developer tools -> Info
- Or use this command: hass --version
-->
**Last working Home Assistant release (if known):**
**UI (States or Lovelace UI?):**
<!--
- Frontend -> Developer tools -> Info
-->
**Browser and Operating System:**
<!--
Provide details about what browser (and version) you are seeing the issue in. And also which operating system this is on. If possible try to replicate the issue in other browsers and include your findings here.
-->
**Description of problem:**
<!--
Explain what the issue is, and how things should look/behave. If possible provide a screenshot with a description.
-->
**Javascript errors shown in the web inspector (if applicable):**
```
```
**Additional information:**

85
.github/ISSUE_TEMPLATE/BUG_REPORT.md vendored Normal file
View File

@@ -0,0 +1,85 @@
---
name: Report a bug with the UI, Frontend or Lovelace
about: Report an issue related to the Home Assistant frontend.
labels: bug
---
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Do not report issues for custom Lovelace cards.
- Provide as many details as possible. Paste logs, configuration samples and code into the backticks.
DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed without comment.
-->
## Checklist
- [ ] I have updated to the latest available Home Assistant version.
- [ ] I have cleared the cache of my browser.
- [ ] I have tried a different browser to see if it is related to my browser.
## The problem
<!--
Describe the issue you are experiencing here to communicate to the
maintainers. Tell us about the current behavior.
If possible provide a screenshot with a description.
-->
## Expected behavior
<!--
Describe what you expected to happen or it should look/behave.
If possible provide a screenshot with a description.
-->
## Steps to reproduce
<!--
Provide steps for us, that helps reproducing your issue.
For example:
1. Add a climate integration
2. Navigate to Lovelace
3. Click more info of the climate entity
4. Set the HVAC action to heat
5. Set the temperature higher than the current temperature
6. Set the HVAC action to cool
-->
## Environment
<!--
Provide details about the versions you are using, which helps us reproducing
and finding the issue quicker. Version information is found in the
Home Assistant frontend: Developer tools -> Info.
Browser version and operating system is important! Please try to replicate
your issue in a different browser and be sure to include your findings.
-->
- Home Assistant release with the issue:
- Last working Home Assistant release (if known):
- UI Type (States or Lovelace):
- Browser and browser version:
- Operating system:
## Problem-relevant configuration
<!--
An example configuration that caused the problem for you. Fill this out even
if it seems unimportant to you. Please be sure to remove personal information
like passwords, private URLs and other credentials.
-->
```yaml
```
## Javascript errors shown in your browser console/inspector
<!--
If you come across any javascript or other error logs, e.g., in your browser
console/inspector please provide them.
-->
```txt
```
## Additional information

View File

@@ -0,0 +1,25 @@
---
name: Request a feature for the UI, Frontend or Lovelace
about: Request an new feature for the Home Assistant frontend.
labels: feature request
---
<!--
DO NOT DELETE ANY TEXT from this template!
Otherwise, your request may be closed without comment.
-->
## The request
<!--
Describe to our maintainers, the feature you would like to be added.
Please be clear and concise and, if possible, provide a screenshot or mockup.
-->
## The alternatives
<!--
Are you currently using, or have you considered alternatives?
If so, could you please describe those?
-->
## Additional information

14
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: Report a bug that is NOT related to the UI, Frontend or Lovelace
url: https://github.com/home-assistant/home-assistant/issues
about: This is the issue tracker for our frontend. Please report other issues with the backend repository.
- name: Report incorrect or missing information on our website
url: https://github.com/home-assistant/home-assistant.io/issues
about: Our documentation has its own issue tracker. Please report issues with the website there.
- name: I have a question or need support
url: https://www.home-assistant.io/help
about: We use GitHub for tracking bugs, check our website for resources on getting help.
- name: I'm unsure where to go
url: https://www.home-assistant.io/join-chat
about: If you are unsure where to go, then joining our chat is recommended; Just ask!

77
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,77 @@
<!--
You are amazing! Thanks for contributing to our project!
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->
## Breaking change
<!--
If your PR contains a breaking change for existing users, it is important
to tell them what breaks, how to make it work again and why we did this.
This piece of text is published with the release notes, so it helps if you
write it towards our users, not us.
Note: Remove this section if this PR is NOT a breaking change.
-->
## Proposed change
<!--
Describe the big picture of your changes here to communicate to the
maintainers why we should accept this pull request. If it fixes a bug
or resolves a feature request, be sure to link to that issue in the
additional information section.
-->
## Type of change
<!--
What type of change does your PR introduce to the Home Assistant frontend?
NOTE: Please, check only 1! box!
If your PR requires multiple boxes to be checked, you'll most likely need to
split it into multiple PRs. This makes things easier and faster to code review.
-->
- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (thank you!)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests
## Example configuration
<!--
Supplying a configuration snippet, makes it easier for a maintainer to test
your PR.
-->
```yaml
```
## Additional information
<!--
Details are important, and help maintainers processing your PR.
Please be sure to fill out additional details, if applicable.
-->
- This PR fixes or closes issue: fixes #
- This PR is related to issue:
- Link to documentation pull request:
## Checklist
<!--
Put an `x` in the boxes that apply. You can also fill these out after
creating the PR. If you're unsure about any of them, don't hesitate to ask.
We're here to help! This is simply a reminder of what we are going to look
for before merging your code.
-->
- [ ] The code change is tested and works locally.
- [ ] There is no commented out code in this PR.
- [ ] Tests have been added to verify that the new code works.
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]
<!--
Thank you for contributing <3
-->
[docs-repository]: https://github.com/home-assistant/home-assistant.io

27
.github/lock.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 1
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: 2020-01-01
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: false
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: false
# Limit to only `issues` or `pulls`
only: pulls
# Optionally, specify configuration settings just for `issues` or `pulls`
issues:
daysUntilLock: 30

56
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 90
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- feature request
- Help wanted
- to do
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: true
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
There hasn't been any activity on this issue recently. Due to the high number
of incoming GitHub notifications, we have to clean some of the old issues,
as many of them have already been resolved with the latest updates.
Please make sure to update to the latest Home Assistant version and check
if that solves the issue. Let us know if that works for you by adding a
comment 👍
This issue now has been marked as stale and will be closed if no further
activity occurs. Thank you for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
# closeComment: >
# Your comment here.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues

View File

@@ -8,19 +8,11 @@ install: yarn install
script:
- npm run build
- hassio/script/build_hassio
# Because else eslint fails because hassio has cleaned that build
- ./node_modules/.bin/gulp gen-icons-app
- npm run test
# - xvfb-run wct --module-resolution=node --npm
# - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi'
services:
- docker
before_deploy:
- 'docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21'
deploy:
provider: script
script: script/travis_deploy
'on':
branch: master
dist: trusty
addons:
sauce_connect: true

View File

@@ -2,9 +2,9 @@
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://home-assistant.io/demo/)
[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://demo.home-assistant.io/)
- [View demo of the Polymer frontend](https://home-assistant.io/demo/)
- [View demo of Home Assistant](https://demo.home-assistant.io/)
- [More information about Home Assistant](https://home-assistant.io)
- [Frontend development instructions](https://developers.home-assistant.io/docs/en/frontend_index.html)
@@ -31,3 +31,5 @@ It is possible to compile the project and/or run commands in the development env
## License
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variation of devices.

View File

@@ -0,0 +1,27 @@
# https://dev.azure.com/home-assistant
trigger: none
pr: none
schedules:
- cron: "0 0 * * *"
displayName: "build preview"
branches:
include:
- dev
always: false
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}
displayName: 'Trigger netlify build preview'

View File

@@ -0,0 +1,70 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
branches:
include:
- dev
paths:
include:
- translations/en.json
pr: none
schedules:
- cron: "30 0 * * *"
displayName: "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'

49
build-scripts/babel.js Normal file
View File

@@ -0,0 +1,49 @@
module.exports.babelLoaderConfig = ({ latestBuild }) => {
if (latestBuild === undefined) {
throw Error("latestBuild not defined for babel loader config");
}
return {
test: /\.m?js$|\.tsx?$/,
use: {
loader: "babel-loader",
options: {
presets: [
!latestBuild && [
require("@babel/preset-env").default,
{ modules: false },
],
[
require("@babel/preset-typescript").default,
{
jsxPragma: "h",
},
],
].filter(Boolean),
plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
[
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/syntax-dynamic-import",
[
"@babel/transform-react-jsx",
{
pragma: "h",
},
],
"@babel/plugin-proposal-optional-chaining",
[
require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true },
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
],
},
},
};
};

6
build-scripts/env.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
isProdBuild: process.env.NODE_ENV === "production",
isStatsBuild: process.env.STATS === "1",
isTravis: process.env.TRAVIS === "true",
isNetlify: process.env.NETLIFY === "true",
};

View File

@@ -1,10 +1,13 @@
// Run HA develop mode
const gulp = require("gulp");
const envVars = require("../env");
require("./clean.js");
require("./translations.js");
require("./gen-icons.js");
require("./gather-static.js");
require("./compress.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
@@ -18,7 +21,7 @@ gulp.task(
"clean",
gulp.parallel(
"gen-service-worker-dev",
"gen-icons",
gulp.parallel("gen-icons-app", "gen-icons-mdi"),
"gen-pages-dev",
"gen-index-app-dev",
gulp.series("create-test-translation", "build-translations")
@@ -35,13 +38,11 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean",
gulp.parallel("gen-icons", "build-translations"),
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
"copy-static",
gulp.parallel(
"webpack-prod-app",
// Do not compress static files in CI, it's SLOW.
...(process.env.CI === "true" ? [] : ["compress-static"])
),
"webpack-prod-app",
...// Don't compress running tests
(envVars.isTravis ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",

View File

@@ -1,4 +1,3 @@
// Run cast develop mode
const gulp = require("gulp");
require("./clean.js");
@@ -16,7 +15,12 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-cast",
gulp.parallel("gen-icons", "gen-index-cast-dev", "build-translations"),
gulp.parallel(
"gen-icons-app",
"gen-icons-mdi",
"gen-index-cast-dev",
"build-translations"
),
"copy-static-cast",
"webpack-dev-server-cast"
)
@@ -29,7 +33,7 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-cast",
gulp.parallel("gen-icons", "build-translations"),
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
"copy-static-cast",
"webpack-prod-cast",
"gen-index-cast-prod"

View File

@@ -9,15 +9,31 @@ gulp.task(
return del([config.root, config.build_dir]);
})
);
gulp.task(
"clean-demo",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.demo_root, config.build_dir]);
})
);
gulp.task(
"clean-cast",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.cast_root, config.build_dir]);
})
);
gulp.task(
"clean-hassio",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.hassio_root, config.build_dir]);
})
);
gulp.task(
"clean-gallery",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.gallery_root, config.build_dir]);
})
);

View File

@@ -0,0 +1,38 @@
// Tasks to compress
const gulp = require("gulp");
const zopfli = require("gulp-zopfli-green");
const merge = require("merge-stream");
const path = require("path");
const paths = require("../paths");
gulp.task("compress-app", function compressApp() {
const jsLatest = gulp
.src(path.resolve(paths.output, "**/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(paths.output));
const jsEs5 = gulp
.src(path.resolve(paths.output_es5, "**/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(paths.output_es5));
const polyfills = gulp
.src(path.resolve(paths.static, "polyfills/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(path.resolve(paths.static, "polyfills")));
const translations = gulp
.src(path.resolve(paths.static, "translations/*.json"))
.pipe(zopfli())
.pipe(gulp.dest(path.resolve(paths.static, "translations")));
return merge(jsLatest, jsEs5, polyfills, translations);
});
gulp.task("compress-hassio", function compressApp() {
return gulp
.src(path.resolve(paths.hassio_root, "**/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(paths.hassio_root));
});

View File

@@ -17,7 +17,8 @@ gulp.task(
},
"clean-demo",
gulp.parallel(
"gen-icons",
"gen-icons-app",
"gen-icons-mdi",
"gen-icons-demo",
"gen-index-demo-dev",
"build-translations"
@@ -34,7 +35,12 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-demo",
gulp.parallel("gen-icons", "gen-icons-demo", "build-translations"),
gulp.parallel(
"gen-icons-app",
"gen-icons-mdi",
"gen-icons-demo",
"build-translations"
),
"copy-static-demo",
"webpack-prod-demo",
"gen-index-demo-prod"

View File

@@ -14,7 +14,7 @@ function hasHtml(data) {
function recursiveCheckHasHtml(file, data, errors, recKey) {
Object.keys(data).forEach(function(key) {
if (typeof data[key] === "object") {
nextRecKey = recKey ? `${recKey}.${key}` : key;
const nextRecKey = recKey ? `${recKey}.${key}` : key;
recursiveCheckHasHtml(file, data[key], errors, nextRecKey);
} else if (hasHtml(data[key])) {
errors.push(`HTML found in ${file.path} at key ${recKey}.${key}`);
@@ -23,7 +23,7 @@ function recursiveCheckHasHtml(file, data, errors, recKey) {
}
function checkHtml() {
let errors = [];
const errors = [];
return mapStream(function(file, cb) {
const content = file.contents;

View File

@@ -11,12 +11,6 @@ const config = require("../paths.js");
const templatePath = (tpl) =>
path.resolve(config.polymer_dir, "src/html/", `${tpl}.html.template`);
const demoTemplatePath = (tpl) =>
path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`);
const castTemplatePath = (tpl) =>
path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`);
const readFile = (pth) => fs.readFileSync(pth).toString();
const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
@@ -25,10 +19,19 @@ const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
};
const renderDemoTemplate = (pth, data = {}) =>
renderTemplate(pth, data, demoTemplatePath);
renderTemplate(pth, data, (tpl) =>
path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`)
);
const renderCastTemplate = (pth, data = {}) =>
renderTemplate(pth, data, castTemplatePath);
renderTemplate(pth, data, (tpl) =>
path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`)
);
const renderGalleryTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(config.gallery_dir, "src/html/", `${tpl}.html.template`)
);
const minifyHtml = (content) =>
minify(content, {
@@ -209,8 +212,33 @@ gulp.task("gen-index-demo-prod", (done) => {
es5Compatibility: es5Manifest["compatibility.js"],
es5DemoJS: es5Manifest["main.js"],
});
const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}");
const minified = minifyHtml(content);
fs.outputFileSync(path.resolve(config.demo_root, "index.html"), minified);
done();
});
gulp.task("gen-index-gallery-dev", (done) => {
// In dev mode we don't mangle names, so we hardcode urls. That way we can
// run webpack as last in watch mode, which blocks output.
const content = renderGalleryTemplate("index", {
latestGalleryJS: "./entrypoint.js",
});
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), content);
done();
});
gulp.task("gen-index-gallery-prod", (done) => {
const latestManifest = require(path.resolve(
config.gallery_output,
"manifest.json"
));
const content = renderGalleryTemplate("index", {
latestGalleryJS: latestManifest["entrypoint.js"],
});
const minified = minifyHtml(content);
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), minified);
done();
});

View File

@@ -0,0 +1,38 @@
// Run demo develop mode
const gulp = require("gulp");
require("./clean.js");
require("./translations.js");
require("./gen-icons.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
gulp.task(
"develop-gallery",
gulp.series(
async function setEnv() {
process.env.NODE_ENV = "development";
},
"clean-gallery",
gulp.parallel("gen-icons-app", "gen-icons-app", "build-translations"),
"copy-static-gallery",
"gen-index-gallery-dev",
"webpack-dev-server-gallery"
)
);
gulp.task(
"build-gallery",
gulp.series(
async function setEnv() {
process.env.NODE_ENV = "production";
},
"clean-gallery",
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
"copy-static-gallery",
"webpack-prod-gallery",
"gen-index-gallery-prod"
)
);

View File

@@ -4,8 +4,6 @@ const gulp = require("gulp");
const path = require("path");
const cpx = require("cpx");
const fs = require("fs-extra");
const zopfli = require("gulp-zopfli-green");
const merge = require("merge-stream");
const paths = require("../paths");
const npmPath = (...parts) =>
@@ -67,20 +65,6 @@ function copyMapPanel(staticDir) {
);
}
function compressStatic(staticDir) {
const staticPath = genStaticPath(staticDir);
const polyfills = gulp
.src(staticPath("polyfills/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(staticPath("polyfills")));
const translations = gulp
.src(staticPath("translations/*.json"))
.pipe(zopfli())
.pipe(gulp.dest(staticPath("translations")));
return merge(polyfills, translations);
}
gulp.task("copy-static", (done) => {
const staticDir = paths.static;
const staticPath = genStaticPath(paths.static);
@@ -100,11 +84,12 @@ gulp.task("copy-static", (done) => {
done();
});
gulp.task("compress-static", () => compressStatic(paths.static));
gulp.task("copy-static-demo", (done) => {
// Copy app static files
fs.copySync(polyPath("public"), paths.demo_root);
fs.copySync(
polyPath("public/static"),
path.resolve(paths.demo_root, "static")
);
// Copy demo static files
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_root);
@@ -126,3 +111,15 @@ gulp.task("copy-static-cast", (done) => {
copyTranslations(paths.cast_static);
done();
});
gulp.task("copy-static-gallery", (done) => {
// Copy app static files
fs.copySync(polyPath("public/static"), paths.gallery_static);
// Copy gallery static files
fs.copySync(path.resolve(paths.gallery_dir, "public"), paths.gallery_root);
copyMapPanel(paths.gallery_static);
copyFonts(paths.gallery_static);
copyTranslations(paths.gallery_static);
done();
});

View File

@@ -57,18 +57,6 @@ function generateIconset(iconsetName, iconNames) {
return `<ha-iconset-svg name="${iconsetName}" size="24"><svg><defs>${iconDefs}</defs></svg></ha-iconset-svg>`;
}
// Generate the full MDI iconset
function genMDIIcons() {
const meta = JSON.parse(
fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8")
);
const iconNames = meta.map((iconInfo) => iconInfo.name);
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR);
}
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames));
}
// Helper function to map recursively over files in a folder and it's subfolders
function mapFiles(startPath, filter, mapFunc) {
const files = fs.readdirSync(startPath);
@@ -101,24 +89,27 @@ function findIcons(searchPath, iconsetName) {
return icons;
}
function genHassIcons() {
gulp.task("gen-icons-mdi", (done) => {
const meta = JSON.parse(
fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8")
);
const iconNames = meta.map((iconInfo) => iconInfo.name);
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR);
}
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames));
done();
});
gulp.task("gen-icons-app", (done) => {
const iconNames = findIcons("./src", "hass");
BUILT_IN_PANEL_ICONS.forEach((name) => iconNames.add(name));
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR);
}
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset("hass", iconNames));
}
gulp.task("gen-icons-mdi", (done) => {
genMDIIcons();
done();
});
gulp.task("gen-icons-hass", (done) => {
genHassIcons();
done();
});
gulp.task("gen-icons", gulp.series("gen-icons-hass", "gen-icons-mdi"));
gulp.task("gen-icons-demo", (done) => {
const iconNames = findIcons(path.resolve(paths.demo_dir, "./src"), "hademo");
@@ -129,8 +120,21 @@ gulp.task("gen-icons-demo", (done) => {
done();
});
module.exports = {
findIcons,
generateIconset,
genMDIIcons,
};
gulp.task("gen-icons-hassio", (done) => {
const iconNames = findIcons(
path.resolve(paths.hassio_dir, "./src"),
"hassio"
);
// Find hassio icons inside HA main repo.
for (const item of findIcons(
path.resolve(paths.polymer_dir, "./src"),
"hassio"
)) {
iconNames.add(item);
}
fs.writeFileSync(
path.resolve(paths.hassio_dir, "hassio-icons.html"),
generateIconset("hassio", iconNames)
);
done();
});

View File

@@ -0,0 +1,34 @@
const gulp = require("gulp");
const envVars = require("../env");
require("./clean.js");
require("./gen-icons.js");
require("./webpack.js");
require("./compress.js");
gulp.task(
"develop-hassio",
gulp.series(
async function setEnv() {
process.env.NODE_ENV = "development";
},
"clean-hassio",
gulp.parallel("gen-icons-hassio", "gen-icons-mdi"),
"webpack-watch-hassio"
)
);
gulp.task(
"build-hassio",
gulp.series(
async function setEnv() {
process.env.NODE_ENV = "production";
},
"clean-hassio",
gulp.parallel("gen-icons-hassio", "gen-icons-mdi"),
"webpack-prod-hassio",
...// Don't compress running tests
(envVars.isTravis ? [] : ["compress-hassio"])
)
);

View File

@@ -45,11 +45,10 @@ function recursiveFlatten(prefix, data) {
let output = {};
Object.keys(data).forEach(function(key) {
if (typeof data[key] === "object") {
output = Object.assign(
{},
output,
recursiveFlatten(prefix + key + ".", data[key])
);
output = {
...output,
...recursiveFlatten(prefix + key + ".", data[key]),
};
} else {
output[prefix + key] = data[key];
}
@@ -99,18 +98,16 @@ function recursiveEmpty(data) {
* @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
*/
const re_key_reference = /\[%key:([^%]+)%\]/;
function lokalise_transform(data, original) {
function lokaliseTransform(data, original, file) {
const output = {};
Object.entries(data).forEach(([key, value]) => {
if (value instanceof Object) {
output[key] = lokalise_transform(value, original);
output[key] = lokaliseTransform(value, original, file);
} else {
output[key] = value.replace(re_key_reference, (match, key) => {
const replace = key.split("::").reduce((tr, k) => tr[k], original);
if (typeof replace !== "string") {
throw Error(
`Invalid key placeholder ${key} in src/translations/en.json`
);
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
}
return replace;
});
@@ -183,7 +180,7 @@ gulp.task(
.src("src/translations/en.json")
.pipe(
transform(function(data, file) {
return lokalise_transform(data, data);
return lokaliseTransform(data, data, file);
})
)
.pipe(rename("translationMaster.json"))
@@ -198,6 +195,11 @@ gulp.task(
gulp.series("build-master-translation", function() {
return gulp
.src([inDir + "/*.json", workDir + "/test.json"], { allowEmpty: true })
.pipe(
transform(function(data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(
foreach(function(stream, file) {
// For each language generate a merged json file. It begins with the master

View File

@@ -1,6 +1,5 @@
// Tasks to run webpack.
const gulp = require("gulp");
const path = require("path");
const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
const log = require("fancy-log");
@@ -9,8 +8,33 @@ const {
createAppConfig,
createDemoConfig,
createCastConfig,
createHassioConfig,
createGalleryConfig,
} = require("../webpack");
const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: true }),
createConfigFunc({ ...params, latestBuild: false }),
];
const runDevServer = ({
compiler,
contentBase,
port,
listenHost = "localhost",
}) =>
new WebpackDevServer(compiler, {
open: true,
watchContentBase: true,
contentBase,
}).listen(port, listenHost, function(err) {
if (err) {
throw err;
}
// Server listening
log("[webpack-dev-server]", `http://localhost:${port}`);
});
const handler = (done) => (err, stats) => {
if (err) {
console.log(err.stack || err);
@@ -32,20 +56,11 @@ const handler = (done) => (err, stats) => {
};
gulp.task("webpack-watch-app", () => {
const compiler = webpack([
createAppConfig({
isProdBuild: false,
latestBuild: true,
isStatsBuild: false,
}),
createAppConfig({
isProdBuild: false,
latestBuild: false,
isStatsBuild: false,
}),
]);
compiler.watch({}, handler());
// we are not calling done, so this command will run forever
webpack(bothBuilds(createAppConfig, { isProdBuild: false })).watch(
{},
handler()
);
});
gulp.task(
@@ -53,47 +68,17 @@ gulp.task(
() =>
new Promise((resolve) =>
webpack(
[
createAppConfig({
isProdBuild: true,
latestBuild: true,
isStatsBuild: false,
}),
createAppConfig({
isProdBuild: true,
latestBuild: false,
isStatsBuild: false,
}),
],
bothBuilds(createAppConfig, { isProdBuild: true }),
handler(resolve)
)
)
);
gulp.task("webpack-dev-server-demo", () => {
const compiler = webpack([
createDemoConfig({
isProdBuild: false,
latestBuild: false,
isStatsBuild: false,
}),
createDemoConfig({
isProdBuild: false,
latestBuild: true,
isStatsBuild: false,
}),
]);
new WebpackDevServer(compiler, {
open: true,
watchContentBase: true,
contentBase: path.resolve(paths.demo_dir, "dist"),
}).listen(8090, "localhost", function(err) {
if (err) {
throw err;
}
// Server listening
log("[webpack-dev-server]", "http://localhost:8090");
runDevServer({
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
contentBase: paths.demo_root,
port: 8090,
});
});
@@ -102,51 +87,22 @@ gulp.task(
() =>
new Promise((resolve) =>
webpack(
[
createDemoConfig({
isProdBuild: true,
latestBuild: false,
isStatsBuild: false,
}),
createDemoConfig({
isProdBuild: true,
latestBuild: true,
isStatsBuild: false,
}),
],
bothBuilds(createDemoConfig, {
isProdBuild: true,
}),
handler(resolve)
)
)
);
gulp.task("webpack-dev-server-cast", () => {
const compiler = webpack([
createCastConfig({
isProdBuild: false,
latestBuild: false,
}),
createCastConfig({
isProdBuild: false,
latestBuild: true,
}),
]);
new WebpackDevServer(compiler, {
open: true,
watchContentBase: true,
contentBase: path.resolve(paths.cast_dir, "dist"),
}).listen(
8080,
runDevServer({
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
contentBase: paths.cast_root,
port: 8080,
// Accessible from the network, because that's how Cast hits it.
"0.0.0.0",
function(err) {
if (err) {
throw err;
}
// Server listening
log("[webpack-dev-server]", "http://localhost:8080");
}
);
listenHost: "0.0.0.0",
});
});
gulp.task(
@@ -154,16 +110,59 @@ gulp.task(
() =>
new Promise((resolve) =>
webpack(
[
createCastConfig({
isProdBuild: true,
latestBuild: false,
}),
createCastConfig({
isProdBuild: true,
latestBuild: true,
}),
],
bothBuilds(createCastConfig, {
isProdBuild: true,
}),
handler(resolve)
)
)
);
gulp.task("webpack-watch-hassio", () => {
// we are not calling done, so this command will run forever
webpack(
createHassioConfig({
isProdBuild: false,
latestBuild: false,
})
).watch({}, handler());
});
gulp.task(
"webpack-prod-hassio",
() =>
new Promise((resolve) =>
webpack(
createHassioConfig({
isProdBuild: true,
latestBuild: false,
}),
handler(resolve)
)
)
);
gulp.task("webpack-dev-server-gallery", () => {
runDevServer({
compiler: webpack(
createGalleryConfig({ latestBuild: true, isProdBuild: false })
),
contentBase: paths.gallery_root,
port: 8100,
});
});
gulp.task(
"webpack-prod-gallery",
() =>
new Promise((resolve) =>
webpack(
createGalleryConfig({
isProdBuild: true,
latestBuild: true,
}),
handler(resolve)
)
)

View File

@@ -20,4 +20,13 @@ module.exports = {
cast_static: path.resolve(__dirname, "../cast/dist/static"),
cast_output: path.resolve(__dirname, "../cast/dist/frontend_latest"),
cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"),
gallery_dir: path.resolve(__dirname, "../gallery"),
gallery_root: path.resolve(__dirname, "../gallery/dist"),
gallery_output: path.resolve(__dirname, "../gallery/dist/frontend_latest"),
gallery_static: path.resolve(__dirname, "../gallery/dist/static"),
hassio_dir: path.resolve(__dirname, "../hassio"),
hassio_root: path.resolve(__dirname, "../hassio/build"),
hassio_publicPath: "/api/hassio/app/",
};

View File

@@ -3,10 +3,9 @@ const fs = require("fs");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const zopfli = require("@gfx/zopfli");
const ManifestPlugin = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const { babelLoaderConfig } = require("./babel.js");
let version = fs
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
@@ -16,273 +15,249 @@ if (!version) {
}
version = version[0];
const genMode = (isProdBuild) => (isProdBuild ? "production" : "development");
const genDevTool = (isProdBuild) =>
isProdBuild ? "source-map" : "inline-cheap-module-source-map";
const genFilename = (isProdBuild, dontHash = new Set()) => ({ chunk }) => {
if (!isProdBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
const createWebpackConfig = ({
entry,
outputRoot,
defineOverlay,
isProdBuild,
latestBuild,
isStatsBuild,
dontHash,
}) => {
if (!dontHash) {
dontHash = new Set();
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
};
const genChunkFilename = (isProdBuild, isStatsBuild) =>
isProdBuild && !isStatsBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
const resolve = {
extensions: [".ts", ".js", ".json", ".tsx"],
alias: {
react: "preact-compat",
"react-dom": "preact-compat",
// Not necessary unless you consume a module using `createClass`
"create-react-class": "preact-compat/lib/create-react-class",
// Not necessary unless you consume a module requiring `react-dom-factories`
"react-dom-factories": "preact-compat/lib/react-dom-factories",
},
};
const tsLoader = (latestBuild) => ({
test: /\.ts|tsx$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader",
options: {
compilerOptions: latestBuild
? { noEmit: false }
: { target: "es5", noEmit: false },
},
},
],
});
const cssLoader = {
test: /\.css$/,
use: "raw-loader",
};
const htmlLoader = {
test: /\.(html)$/,
use: {
loader: "html-loader",
options: {
exportAsEs6Default: true,
},
},
};
const plugins = [
// Ignore moment.js locales
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Color.js is bloated, it contains all color definitions for all material color sets.
new webpack.NormalModuleReplacementPlugin(
/@polymer\/paper-styles\/color\.js$/,
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// Ignore roboto pointing at CDN. We use local font-roboto-local.
new webpack.NormalModuleReplacementPlugin(
/@polymer\/font-roboto\/roboto\.js$/,
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// Ignore mwc icons pointing at CDN.
new webpack.NormalModuleReplacementPlugin(
/@material\/mwc-icon\/mwc-icon-font\.js$/,
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
];
const optimization = (latestBuild) => ({
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
extractComments: true,
sourceMap: true,
terserOptions: {
safari10: true,
ecma: latestBuild ? undefined : 5,
},
}),
],
});
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
const isCI = process.env.CI === "true";
// Create an object mapping browser urls to their paths during build
const translationMetadata = require("../build-translations/translationMetadata.json");
const workBoxTranslationsTemplatedURLs = {};
const englishFP = translationMetadata.translations.en.fingerprints;
Object.keys(englishFP).forEach((key) => {
workBoxTranslationsTemplatedURLs[
`/static/translations/${englishFP[key]}`
] = `build-translations/output/${key}.json`;
});
const entry = {
app: "./src/entrypoints/app.ts",
authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts",
core: "./src/entrypoints/core.ts",
compatibility: "./src/entrypoints/compatibility.ts",
"custom-panel": "./src/entrypoints/custom-panel.ts",
"hass-icons": "./src/entrypoints/hass-icons.ts",
};
return {
mode: genMode(isProdBuild),
devtool: genDevTool(isProdBuild),
mode: isProdBuild ? "production" : "development",
devtool: isProdBuild ? "source-map" : "inline-cheap-module-source-map",
entry,
module: {
rules: [tsLoader(latestBuild), cssLoader, htmlLoader],
rules: [
babelLoaderConfig({ latestBuild }),
{
test: /\.css$/,
use: "raw-loader",
},
{
test: /\.(html)$/,
use: {
loader: "html-loader",
options: {
exportAsEs6Default: true,
},
},
},
],
},
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
extractComments: true,
sourceMap: true,
terserOptions: {
safari10: true,
ecma: latestBuild ? undefined : 5,
},
}),
],
},
optimization: optimization(latestBuild),
plugins: [
new ManifestPlugin(),
new webpack.DefinePlugin({
__DEV__: JSON.stringify(!isProdBuild),
__DEMO__: false,
__DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(version),
__DEMO__: false,
__STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development"
),
...defineOverlay,
}),
...plugins,
isProdBuild &&
!isCI &&
!isStatsBuild &&
new CompressionPlugin({
cache: true,
exclude: [/\.js\.map$/, /\.LICENSE$/, /\.py$/, /\.txt$/],
algorithm(input, compressionOptions, callback) {
return zopfli.gzip(input, compressionOptions, callback);
},
}),
latestBuild &&
new WorkboxPlugin.InjectManifest({
swSrc: "./src/entrypoints/service-worker-hass.js",
swDest: "service_worker.js",
importWorkboxFrom: "local",
include: [/\.js$/],
templatedURLs: {
...workBoxTranslationsTemplatedURLs,
"/static/icons/favicon-192x192.png":
"public/icons/favicon-192x192.png",
"/static/fonts/roboto/Roboto-Light.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2",
"/static/fonts/roboto/Roboto-Medium.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2",
"/static/fonts/roboto/Roboto-Regular.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2",
"/static/fonts/roboto/Roboto-Bold.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2",
},
}),
// Ignore moment.js locales
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Color.js is bloated, it contains all color definitions for all material color sets.
new webpack.NormalModuleReplacementPlugin(
/@polymer\/paper-styles\/color\.js$/,
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// Ignore roboto pointing at CDN. We use local font-roboto-local.
new webpack.NormalModuleReplacementPlugin(
/@polymer\/font-roboto\/roboto\.js$/,
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// Ignore mwc icons pointing at CDN.
new webpack.NormalModuleReplacementPlugin(
/@material\/mwc-icon\/mwc-icon-font\.js$/,
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
].filter(Boolean),
resolve: {
extensions: [".ts", ".js", ".json"],
alias: {
react: "preact-compat",
"react-dom": "preact-compat",
// Not necessary unless you consume a module using `createClass`
"create-react-class": "preact-compat/lib/create-react-class",
// Not necessary unless you consume a module requiring `react-dom-factories`
"react-dom-factories": "preact-compat/lib/react-dom-factories",
},
},
output: {
filename: genFilename(isProdBuild),
chunkFilename: genChunkFilename(isProdBuild, isStatsBuild),
path: latestBuild ? paths.output : paths.output_es5,
filename: ({ chunk }) => {
if (!isProdBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
},
chunkFilename:
isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js"
: "[name].chunk.js",
path: path.resolve(
outputRoot,
latestBuild ? "frontend_latest" : "frontend_es5"
),
publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/",
// For workerize loader
globalObject: "self",
},
resolve,
};
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
const config = createWebpackConfig({
entry: {
app: "./src/entrypoints/app.ts",
authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts",
core: "./src/entrypoints/core.ts",
compatibility: "./src/entrypoints/compatibility.ts",
"custom-panel": "./src/entrypoints/custom-panel.ts",
"hass-icons": "./src/entrypoints/hass-icons.ts",
},
outputRoot: paths.root,
isProdBuild,
latestBuild,
isStatsBuild,
});
if (latestBuild) {
// Create an object mapping browser urls to their paths during build
const translationMetadata = require("../build-translations/translationMetadata.json");
const workBoxTranslationsTemplatedURLs = {};
const englishFP = translationMetadata.translations.en.fingerprints;
Object.keys(englishFP).forEach((key) => {
workBoxTranslationsTemplatedURLs[
`/static/translations/${englishFP[key]}`
] = `build-translations/output/${key}.json`;
});
config.plugins.push(
new WorkboxPlugin.InjectManifest({
swSrc: "./src/entrypoints/service-worker-hass.js",
swDest: "service_worker.js",
importWorkboxFrom: "local",
include: [/\.js$/],
templatedURLs: {
...workBoxTranslationsTemplatedURLs,
"/static/icons/favicon-192x192.png":
"public/icons/favicon-192x192.png",
"/static/fonts/roboto/Roboto-Light.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2",
"/static/fonts/roboto/Roboto-Medium.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2",
"/static/fonts/roboto/Roboto-Regular.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2",
"/static/fonts/roboto/Roboto-Bold.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2",
},
})
);
}
return config;
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return {
mode: genMode(isProdBuild),
devtool: genDevTool(isProdBuild),
return createWebpackConfig({
entry: {
main: "./demo/src/entrypoint.ts",
compatibility: "./src/entrypoints/compatibility.ts",
},
module: {
rules: [tsLoader(latestBuild), cssLoader, htmlLoader],
},
optimization: optimization(latestBuild),
plugins: [
new ManifestPlugin(),
new webpack.DefinePlugin({
__DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(`DEMO-${version}`),
__DEMO__: true,
__STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development"
),
}),
...plugins,
].filter(Boolean),
resolve,
output: {
filename: genFilename(isProdBuild),
chunkFilename: genChunkFilename(isProdBuild, isStatsBuild),
path: path.resolve(
paths.demo_root,
latestBuild ? "frontend_latest" : "frontend_es5"
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
compatibility: path.resolve(
paths.polymer_dir,
"src/entrypoints/compatibility.ts"
),
publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/",
// For workerize loader
globalObject: "self",
},
};
outputRoot: paths.demo_root,
defineOverlay: {
__VERSION__: JSON.stringify(`DEMO-${version}`),
__DEMO__: true,
},
isProdBuild,
latestBuild,
isStatsBuild,
});
};
const createCastConfig = ({ isProdBuild, latestBuild }) => {
const isStatsBuild = false;
const entry = {
launcher: "./cast/src/launcher/entrypoint.ts",
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
};
if (latestBuild) {
entry.receiver = "./cast/src/receiver/entrypoint.ts";
entry.receiver = path.resolve(paths.cast_dir, "src/receiver/entrypoint.ts");
}
return {
mode: genMode(isProdBuild),
devtool: genDevTool(isProdBuild),
return createWebpackConfig({
entry,
module: {
rules: [tsLoader(latestBuild), cssLoader, htmlLoader],
outputRoot: paths.cast_root,
isProdBuild,
latestBuild,
});
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
if (latestBuild) {
throw new Error("Hass.io does not support latest build!");
}
const config = createWebpackConfig({
entry: {
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
},
optimization: optimization(latestBuild),
plugins: [
new ManifestPlugin(),
new webpack.DefinePlugin({
__DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(version),
__DEMO__: false,
__STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development"
),
}),
...plugins,
].filter(Boolean),
resolve,
output: {
filename: genFilename(isProdBuild),
chunkFilename: genChunkFilename(isProdBuild, isStatsBuild),
path: path.resolve(
paths.cast_root,
latestBuild ? "frontend_latest" : "frontend_es5"
),
publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/",
// For workerize loader
globalObject: "self",
outputRoot: "",
isProdBuild,
latestBuild,
dontHash: new Set(["entrypoint"]),
});
config.output.path = paths.hassio_root;
config.output.publicPath = paths.hassio_publicPath;
return config;
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
if (!latestBuild) {
throw new Error("Gallery only supports latest build!");
}
const config = createWebpackConfig({
entry: {
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
},
};
outputRoot: paths.gallery_root,
isProdBuild,
latestBuild,
});
return config;
};
module.exports = {
resolve,
plugins,
optimization,
createAppConfig,
createDemoConfig,
createCastConfig,
createHassioConfig,
createGalleryConfig,
};

20
cast/public/_headers Normal file
View File

@@ -0,0 +1,20 @@
/*
Cache-Control: public, max-age: 0, s-maxage=3600, must-revalidate
Content-Security-Policy: form-action https:
Feature-Policy: vibrate 'none'; geolocation 'none'; midi 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; vibrate 'none'; payment 'none'
Referrer-Policy: no-referrer-when-downgrade
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
/images/*
Cache-Control: public, max-age: 604800, s-maxage=604800
/manifest.json
Cache-Control: public, max-age: 3600, s-maxage=3600
/frontend_es5/*
Cache-Control: public, max-age: 604800, s-maxage=604800
/frontend_latest/*
Cache-Control: public, max-age: 604800, s-maxage=604800

View File

@@ -39,7 +39,7 @@ class HcCast extends LitElement {
@property() private askWrite = false;
@property() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
if (this.lovelaceConfig === undefined) {
return html`
<loading-screen></loading-screen>>

View File

@@ -70,7 +70,7 @@ export class HcConnect extends LitElement {
@property() private castManager?: CastManager | null;
private openDemo = false;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
if (this.cannotConnect) {
const tokens = loadTokens();
return html`

View File

@@ -22,7 +22,7 @@ class HcLayout extends LitElement {
@property() public connection?: Connection;
@property() public user?: HassUser;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
return html`
<ha-card>
<div class="layout">
@@ -50,13 +50,12 @@ class HcLayout extends LitElement {
</div>
</ha-card>
<div class="footer">
<a href="./faq.html">Frequently Asked Questions</a> Found a bug? Let
@balloob know
<!-- <a
<a href="./faq.html">Frequently Asked Questions</a> Found a bug?
<a
href="https://github.com/home-assistant/home-assistant-polymer/issues"
target="_blank"
>Let us know!</a
> -->
>
</div>
`;
}

View File

@@ -16,7 +16,7 @@ class HcDemo extends HassElement {
@property() public lovelacePath!: string;
@property() private _lovelaceConfig?: LovelaceConfig;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
if (!this._lovelaceConfig) {
return html``;
}

View File

@@ -14,7 +14,7 @@ class HcLaunchScreen extends LitElement {
@property() public hass?: HomeAssistant;
@property() public error?: string;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
return html`
<div class="container">
<img

View File

@@ -8,7 +8,8 @@ import {
property,
} from "lit-element";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import "../../../../src/panels/lovelace/hui-view";
import "../../../../src/panels/lovelace/views/hui-view";
import "../../../../src/panels/lovelace/views/hui-panel-view";
import { HomeAssistant } from "../../../../src/types";
import { Lovelace } from "../../../../src/panels/lovelace/types";
import "./hc-launch-screen";
@@ -21,7 +22,7 @@ class HcLovelace extends LitElement {
@property() public viewPath?: string | number;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
const index = this._viewIndex;
if (index === undefined) {
return html`
@@ -38,16 +39,24 @@ class HcLovelace extends LitElement {
mode: "storage",
language: "en",
saveConfig: async () => undefined,
deleteConfig: async () => undefined,
setEditMode: () => undefined,
};
return html`
<hui-view
.hass=${this.hass}
.lovelace=${lovelace}
.index=${index}
columns="2"
></hui-view>
`;
return this.lovelaceConfig.views[index].panel
? html`
<hui-panel-view
.hass=${this.hass}
.config=${this.lovelaceConfig.views[index]}
></hui-panel-view>
`
: html`
<hui-view
.hass=${this.hass}
.lovelace=${lovelace}
.index=${index}
columns="2"
></hui-view>
`;
}
protected updated(changedProps) {
@@ -62,7 +71,9 @@ class HcLovelace extends LitElement {
this.lovelaceConfig.background;
if (configBackground) {
this.shadowRoot!.querySelector("hui-view")!.style.setProperty(
(this.shadowRoot!.querySelector(
"hui-view, hui-panel-view"
) as HTMLElement)!.style.setProperty(
"--lovelace-background",
configBackground
);
@@ -94,7 +105,7 @@ class HcLovelace extends LitElement {
box-sizing: border-box;
background: var(--primary-background-color);
}
hui-view {
:host > * {
flex: 1;
}
`;

View File

@@ -50,7 +50,7 @@ export class HcMain extends HassElement {
}
}
protected render(): TemplateResult | void {
protected render(): TemplateResult {
if (this._showDemo) {
return html`
<hc-demo .lovelacePath=${this._lovelacePath}></hc-demo>
@@ -137,14 +137,14 @@ export class HcMain extends HassElement {
}),
});
} catch (err) {
this._error = err;
this._error = this._getErrorMessage(err);
return;
}
let connection;
try {
connection = await createConnection({ auth });
} catch (err) {
this._error = err;
this._error = this._getErrorMessage(err);
return;
}
if (this.hass) {
@@ -175,9 +175,9 @@ export class HcMain extends HassElement {
} catch (err) {
// Generate a Lovelace config.
this._unsubLovelace = () => undefined;
const {
generateLovelaceConfigFromHass,
} = await import("../../../../src/panels/lovelace/common/generate-lovelace-config");
const { generateLovelaceConfigFromHass } = await import(
"../../../../src/panels/lovelace/common/generate-lovelace-config"
);
this._handleNewLovelaceConfig(
await generateLovelaceConfigFromHass(this.hass!)
);
@@ -213,6 +213,23 @@ export class HcMain extends HassElement {
});
}
private _getErrorMessage(error: number): string {
switch (error) {
case 1:
return "Unable to connect to the Home Assistant websocket API.";
case 2:
return "The supplied authentication is invalid.";
case 3:
return "The connection to Home Assistant was lost.";
case 4:
return "Missing hassUrl. This is required.";
case 5:
return "Home Assistant needs to be served over https:// to use with Home Assistant Cast.";
default:
return "Unknown Error";
}
}
private _breakFree() {
const controls = document.body.querySelector("touch-controls");
if (controls) {

11
cast/webpack.config.js Normal file
View File

@@ -0,0 +1,11 @@
const { createCastConfig } = require("../build-scripts/webpack.js");
const { isProdBuild } = require("../build-scripts/env.js");
// File just used for stats builds
const latestBuild = true;
module.exports = createCastConfig({
isProdBuild,
latestBuild,
});

18
demo/public/_headers Normal file
View File

@@ -0,0 +1,18 @@
/*
Cache-Control: public, max-age: 0, s-maxage=3600, must-revalidate
Content-Security-Policy: form-action https:
Referrer-Policy: no-referrer-when-downgrade
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
/api/*
Cache-Control: public, max-age: 604800, s-maxage=604800
/assets/*
Cache-Control: public, max-age: 604800, s-maxage=604800
/frontend_es5/*
Cache-Control: public, max-age: 604800, s-maxage=604800
/frontend_latest/*
Cache-Control: public, max-age: 604800, s-maxage=604800

View File

@@ -7,22 +7,26 @@
{
"src": "/static/icons/favicon-192x192.png",
"sizes": "192x192",
"type": "image/png"
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/static/icons/favicon-384x384.png",
"sizes": "384x384",
"type": "image/png"
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/static/icons/favicon-512x512.png",
"sizes": "512x512",
"type": "image/png"
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/static/icons/favicon-1024x1024.png",
"sizes": "1024x1024",
"type": "image/png"
"type": "image/png",
"purpose": "maskable any"
}
],
"lang": "en-US",

View File

@@ -115,10 +115,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
attributes: {
friendly_name: "Abode Updates",
icon: "hademo:security",
templates: {
icon_color:
"if (state === 'on') return 'rgb(251, 210, 41)'; return 'rgb(54, 95, 140)';\n",
},
},
},
"input_boolean.tv": {
@@ -127,10 +123,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
attributes: {
friendly_name: "TV",
icon: "hademo:television",
templates: {
icon_color:
"if (state === 'on') return 'rgb(251, 210, 41)'; return 'rgb(54, 95, 140)';\n",
},
},
},
"input_boolean.homeautomation": {
@@ -139,10 +131,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
attributes: {
friendly_name: "Home Automation",
icon: "hass:home-automation",
templates: {
icon_color:
"if (state === 'on') return 'rgb(251, 210, 41)'; return 'rgb(54, 95, 140)';\n",
},
},
},
"input_boolean.tvtime": {
@@ -151,12 +139,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
attributes: {
friendly_name: "TV Time",
icon: "hademo:television-guide",
templates: {
icon:
"if (state === 'on') return 'hademo:television-classic'; return 'hademo:television-classic-off';\n",
icon_color:
"if (state === 'on') return 'rgb(251, 210, 41)'; return 'rgb(54, 95, 140)';\n",
},
},
},
"input_select.livingroomharmony": {
@@ -235,6 +217,18 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
icon: "hademo:currency-usd",
},
},
"sensor.study_temp": {
entity_id: "sensor.study_temp",
state: "20.9",
attributes: {
unit_of_measurement: "°C",
device_class: "temperature",
friendly_name: localize(
"ui.panel.page-demo.config.arsaboo.names.temperature_study"
),
icon: "hademo:thermometer",
},
},
"cover.garagedoor": {
entity_id: "cover.garagedoor",
state: "closed",
@@ -560,12 +554,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "off",
attributes: {
friendly_name: "Driveway Light",
templates: {
icon_color:
"if (state === 'on') return 'rgb(251, 210, 41)'; return 'rgb(54, 95, 140)';\n",
icon:
"if (state === 'on') return 'hademo:lightbulb-on'; return 'hademo:lightbulb';\n",
},
},
},
"switch.wemoporch": {
@@ -573,12 +561,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "off",
attributes: {
friendly_name: "Porch Lights",
templates: {
icon_color:
"if (state === 'on') return 'rgb(251, 210, 41)'; return 'rgb(54, 95, 140)';\n",
icon:
"if (state === 'on') return 'hademo:lightbulb-on'; return 'hademo:lightbulb';\n",
},
},
},
});

View File

@@ -446,6 +446,11 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
"script.tv_off",
],
},
{
type: "sensor",
entity: "sensor.study_temp",
graph: "line",
},
{
type: "entities",
title: "Doorbell",

View File

@@ -23,27 +23,24 @@ export const demoThemeJimpower = () => ({
"paper-listbox-background-color": "#2E333A",
"table-row-background-color": "#353840",
"paper-grey-50": "var(--primary-text-color)",
"paper-toggle-button-checked-button-color": "var(--accent-color)",
"switch-checked-color": "var(--accent-color)",
"paper-dialog-background-color": "#434954",
"secondary-text-color": "#5294E2",
"google-red-500": "#E45E65",
"divider-color": "rgba(0, 0, 0, .12)",
"paper-toggle-button-unchecked-ink-color": "var(--disabled-text-color)",
"google-green-500": "#39E949",
"paper-toggle-button-unchecked-button-color": "var(--disabled-text-color)",
"switch-unchecked-button-color": "var(--disabled-text-color)",
"label-badge-border-color": "green",
"paper-listbox-color": "var(--primary-color)",
"paper-slider-disabled-secondary-color": "var(--disabled-text-color)",
"paper-toggle-button-checked-ink-color": "var(--accent-color)",
"paper-card-background-color": "#434954",
"label-badge-text-color": "var(--primary-text-color)",
"paper-slider-knob-start-color": "var(--accent-color)",
"paper-toggle-button-unchecked-bar-color": "var(--disabled-text-color)",
"switch-unchecked-track-color": "var(--disabled-text-color)",
"dark-primary-color": "var(--accent-color)",
"paper-slider-secondary-color": "var(--secondary-background-color)",
"paper-slider-pin-color": "var(--accent-color)",
"paper-item-icon-active-color": "#F9C536",
"accent-color": "#E45E65",
"paper-toggle-button-checked-bar-color": "var(--accent-color)",
"table-row-alternative-background-color": "#3E424B",
});

View File

@@ -24,27 +24,24 @@ export const demoThemeKernehed = () => ({
"paper-listbox-background-color": "#141414",
"table-row-background-color": "#292929",
"paper-grey-50": "var(--primary-text-color)",
"paper-toggle-button-checked-button-color": "var(--accent-color)",
"switch-checked-color": "var(--accent-color)",
"paper-dialog-background-color": "#292929",
"secondary-text-color": "#b58e31",
"google-red-500": "#b58e31",
"divider-color": "rgba(0, 0, 0, .12)",
"paper-toggle-button-unchecked-ink-color": "var(--disabled-text-color)",
"google-green-500": "#2980b9",
"paper-toggle-button-unchecked-button-color": "var(--disabled-text-color)",
"switch-unchecked-button-color": "var(--disabled-text-color)",
"label-badge-border-color": "green",
"paper-listbox-color": "#777777",
"paper-slider-disabled-secondary-color": "var(--disabled-text-color)",
"paper-toggle-button-checked-ink-color": "var(--accent-color)",
"paper-card-background-color": "#292929",
"label-badge-text-color": "var(--primary-text-color)",
"paper-slider-knob-start-color": "var(--accent-color)",
"paper-toggle-button-unchecked-bar-color": "var(--disabled-text-color)",
"switch-unchecked-track-color": "var(--disabled-text-color)",
"dark-primary-color": "var(--accent-color)",
"paper-slider-secondary-color": "var(--secondary-background-color)",
"paper-slider-pin-color": "var(--accent-color)",
"paper-item-icon-active-color": "#b58e31",
"accent-color": "#2980b9",
"paper-toggle-button-checked-bar-color": "var(--accent-color)",
"table-row-alternative-background-color": "#292929",
});

View File

@@ -12,8 +12,7 @@ export const demoThemeTeachingbirds = () => ({
"paper-slider-knob-color": "var(--primary-color)",
"paper-listbox-color": "#FFFFFF",
"paper-toggle-button-checked-bar-color": "var(--light-primary-color)",
"paper-toggle-button-checked-ink-color": "var(--dark-primary-color)",
"paper-toggle-button-unchecked-bar-color": "var(--primary-text-color)",
"switch-unchecked-track-color": "var(--primary-text-color)",
"paper-card-background-color": "#4e4e4e",
"label-badge-text-color": "var(--text-primary-color)",
"primary-background-color": "#303030",
@@ -22,7 +21,7 @@ export const demoThemeTeachingbirds = () => ({
"secondary-background-color": "#2b2b2b",
"paper-slider-knob-start-color": "var(--primary-color)",
"paper-item-icon-active-color": "#d8bf50",
"paper-toggle-button-checked-button-color": "var(--primary-color)",
"switch-checked-color": "var(--primary-color)",
"secondary-text-color": "#389638",
"disabled-text-color": "#545454",
"paper-item-icon_-_color": "var(--primary-text-color)",

View File

@@ -53,7 +53,7 @@ class CardModder extends LitElement {
for (var k in this._config.style) {
if (window.cardTools.hasTemplate(this._config.style[k]))
this.templated.push(k);
this.card.style.setProperty(k, '');
this.card.style.setProperty(k, "");
target.style.setProperty(
k,
window.cardTools.parseTemplate(this._config.style[k])

View File

@@ -161,8 +161,8 @@ if (!window.cardTools) {
};
cardTools.longpress = (element) => {
customElements.whenDefined("long-press").then(() => {
const longpress = document.body.querySelector("long-press");
customElements.whenDefined("action-handler").then(() => {
const longpress = document.body.querySelector("action-handler");
longpress.bind(element);
});
return element;

View File

@@ -10,7 +10,7 @@ import {
import "../../../src/components/ha-icon";
import {
EntityRow,
LovelaceRow,
CastConfig,
} from "../../../src/panels/lovelace/entity-rows/types";
import { HomeAssistant } from "../../../src/types";
@@ -18,7 +18,7 @@ import { CastManager } from "../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../src/cast/receiver_messages";
@customElement("cast-demo-row")
class CastDemoRow extends LitElement implements EntityRow {
class CastDemoRow extends LitElement implements LovelaceRow {
public hass!: HomeAssistant;
@property() private _castManager?: CastManager | null;
@@ -27,7 +27,7 @@ class CastDemoRow extends LitElement implements EntityRow {
// No config possible.
}
protected render(): TemplateResult | void {
protected render(): TemplateResult {
if (
!this._castManager ||
this._castManager.castState === "NO_DEVICES_AVAILABLE"

View File

@@ -1,10 +1,4 @@
import {
LitElement,
html,
CSSResult,
css,
PropertyDeclarations,
} from "lit-element";
import { LitElement, html, CSSResult, css, property } from "lit-element";
import { until } from "lit-html/directives/until";
import "@material/mwc-button";
import "@polymer/paper-spinner/paper-spinner-lite";
@@ -20,19 +14,11 @@ import {
} from "../configs/demo-configs";
export class HADemoCard extends LitElement implements LovelaceCard {
public lovelace?: Lovelace;
public hass!: MockHomeAssistant;
private _switching?: boolean;
@property() public lovelace?: Lovelace;
@property() public hass!: MockHomeAssistant;
@property() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card;
static get properties(): PropertyDeclarations {
return {
lovelace: {},
hass: {},
_switching: {},
};
}
public getCardSize() {
return this._hidden ? 0 : 2;
}

View File

@@ -12,5 +12,7 @@ import "./resources/hademo-icons";
/* polyfill for paper-dropdown */
setTimeout(() => {
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
);
}, 1000);

View File

@@ -65,74 +65,79 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"];
export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI(new RegExp("history/period/.+"), (
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
mockHass.mockAPI(
new RegExp("history/period/.+"),
(
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
const results: HassEntity[][] = [];
const results: HassEntity[][] = [];
for (const entityId of entities) {
const state = hass.states[entityId];
for (const entityId of entities) {
const state = hass.states[entityId];
if (!state) {
continue;
}
if (!state) {
continue;
}
if (!state.attributes.unit_of_measurement) {
results.push(generateHistory(state, [state.state]));
continue;
}
if (!state.attributes.unit_of_measurement) {
results.push(generateHistory(state, [state.state]));
continue;
}
const numberState = Number(state.state);
const numberState = Number(state.state);
if (isNaN(numberState)) {
// tslint:disable-next-line
console.log(
"Ignoring state with unparsable state but with a unit",
entityId,
state
if (isNaN(numberState)) {
// tslint:disable-next-line
console.log(
"Ignoring state with unparsable state but with a unit",
entityId,
state
);
continue;
}
const statesToGenerate = 15;
let genFunc;
if (incrementalUnits.includes(state.attributes.unit_of_measurement)) {
let initial = Math.floor(
numberState * 0.4 + numberState * Math.random() * 0.2
);
const diff = Math.max(
1,
Math.floor((numberState - initial) / statesToGenerate)
);
genFunc = () => {
initial += diff;
return Math.min(numberState, initial);
};
} else {
const diff = Math.floor(
numberState * (numberState > 80 ? 0.05 : 0.5)
);
genFunc = () =>
numberState - diff + Math.floor(Math.random() * 2 * diff);
}
results.push(
generateHistory(
{
entity_id: state.entity_id,
attributes: state.attributes,
},
Array.from({ length: statesToGenerate }, genFunc)
)
);
continue;
}
const statesToGenerate = 15;
let genFunc;
if (incrementalUnits.includes(state.attributes.unit_of_measurement)) {
let initial = Math.floor(
numberState * 0.4 + numberState * Math.random() * 0.2
);
const diff = Math.max(
1,
Math.floor((numberState - initial) / statesToGenerate)
);
genFunc = () => {
initial += diff;
return Math.min(numberState, initial);
};
} else {
const diff = Math.floor(numberState * (numberState > 80 ? 0.05 : 0.5));
genFunc = () =>
numberState - diff + Math.floor(Math.random() * 2 * diff);
}
results.push(
generateHistory(
{
entity_id: state.entity_id,
attributes: state.attributes,
},
Array.from({ length: statesToGenerate }, genFunc)
)
);
return results;
}
return results;
});
);
};

View File

@@ -12,9 +12,10 @@ export const mockLovelace = (
localizePromise: Promise<LocalizeFunc>
) => {
hass.mockWS("lovelace/config", () =>
Promise.all([selectedDemoConfig, localizePromise]).then(
([config, localize]) => config.lovelace(localize)
)
Promise.all([
selectedDemoConfig,
localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
);
hass.mockWS("lovelace/config/save", () => Promise.resolve());

View File

@@ -97,7 +97,7 @@ export const mockTranslations = (hass: MockHomeAssistant) => {
"component.nest.config.abort.authorize_url_timeout":
"Timeout generating authorize url.",
"component.nest.config.abort.no_flows":
"You need to configure Nest before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/nest/).",
"You need to configure Nest before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/integrations/nest/).",
"component.nest.config.error.internal_error":
"Internal error validating code",
"component.nest.config.error.invalid_code": "Invalid code",
@@ -199,7 +199,7 @@ export const mockTranslations = (hass: MockHomeAssistant) => {
"component.point.config.abort.external_setup":
"Point successfully configured from another flow.",
"component.point.config.abort.no_flows":
"You need to configure Point before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/point/).",
"You need to configure Point before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/integrations/point/).",
"component.point.config.create_entry.default":
"Successfully authenticated with Minut for your Point device(s)",
"component.point.config.error.follow_link":

View File

@@ -1,10 +1,9 @@
const { createDemoConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
// This file exists because we haven't migrated the stats script yet
// File just used for stats builds
const isProdBuild = process.env.NODE_ENV === "production";
const isStatsBuild = process.env.STATS === "1";
const latestBuild = false;
const latestBuild = true;
module.exports = createDemoConfig({
isProdBuild,

View File

@@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#2157BC">
<title>HAGallery</title>
<script src='./main.js' async></script>
<style>
body {
font-family: Roboto, Noto, sans-serif;
margin: 0;
padding: 0;
}
</style>
</head>
<body></body>
</html>

View File

@@ -4,14 +4,6 @@
# Stop on errors
set -e
cd "$(dirname "$0")/.."
cd "$(dirname "$0")/../.."
OUTPUT_DIR=dist
rm -rf $OUTPUT_DIR
cd ..
./node_modules/.bin/gulp build-translations gen-icons
cd gallery
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js
./node_modules/.bin/gulp build-gallery

View File

@@ -4,10 +4,6 @@
# Stop on errors
set -e
cd "$(dirname "$0")/.."
cd "$(dirname "$0")/../.."
cd ..
./node_modules/.bin/gulp build-translations gen-icons
cd gallery
../node_modules/.bin/webpack-dev-server
./node_modules/.bin/gulp develop-gallery

View File

@@ -1,8 +1,8 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import JsYaml from "js-yaml";
import { safeLoad } from "js-yaml";
import { createCardElement } from "../../../src/panels/lovelace/common/create-card-element";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
class DemoCard extends PolymerElement {
static get template() {
@@ -62,7 +62,7 @@ class DemoCard extends PolymerElement {
card.removeChild(card.lastChild);
}
const el = createCardElement(JsYaml.safeLoad(config.config)[0]);
const el = createCardElement(safeLoad(config.config)[0]);
el.hass = this.hass;
card.appendChild(el);
}

View File

@@ -1,9 +1,9 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-toggle-button/paper-toggle-button";
import "./demo-card";
import "../../../src/components/ha-switch";
class DemoCards extends PolymerElement {
static get template() {
@@ -26,9 +26,9 @@ class DemoCards extends PolymerElement {
</style>
<app-toolbar>
<div class="filters">
<paper-toggle-button checked="{{_showConfig}}"
>Show config</paper-toggle-button
>
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
Show config
</ha-switch>
</div>
</app-toolbar>
<div class="cards">
@@ -53,6 +53,10 @@ class DemoCards extends PolymerElement {
},
};
}
_showConfigToggled(ev) {
this._showConfig = ev.target.checked;
}
}
customElements.define("demo-cards", DemoCards);

View File

@@ -1,9 +1,9 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-toggle-button/paper-toggle-button";
import "./demo-more-info";
import "../../../src/components/ha-switch";
class DemoMoreInfos extends PolymerElement {
static get template() {
@@ -26,9 +26,7 @@ class DemoMoreInfos extends PolymerElement {
</style>
<app-toolbar>
<div class="filters">
<paper-toggle-button checked="{{_showConfig}}"
>Show entity</paper-toggle-button
>
<ha-switch checked="{{_showConfig}}">Show entity</ha-switch>
</div>
</app-toolbar>
<div class="cards">

View File

@@ -36,7 +36,7 @@ export default {
attributes: {
title: "Welcome Home!",
message:
"Here are some resources to get started:\n\n - [Configuring Home Assistant](https://home-assistant.io/getting-started/configuration/)\n - [Available components](https://home-assistant.io/components/)\n - [Troubleshooting your configuration](https://home-assistant.io/docs/configuration/troubleshooting/)\n - [Getting help](https://home-assistant.io/help/)\n\nTo not see this card popup in the future, edit your config in\n`configuration.yaml` and disable the `introduction` component.",
"Here are some resources to get started:\n\n - [Configuring Home Assistant](https://home-assistant.io/getting-started/configuration/)\n - [Available integrations](https://home-assistant.io/integrations/)\n - [Troubleshooting your configuration](https://home-assistant.io/docs/configuration/troubleshooting/)\n - [Getting help](https://home-assistant.io/help/)\n\nTo not see this card popup in the future, edit your config in\n`configuration.yaml` and disable the `introduction` integration.",
},
last_changed: "2018-07-19T10:44:45.922241+00:00",
last_updated: "2018-07-19T10:44:45.922241+00:00",

View File

@@ -2,19 +2,19 @@ import { html, LitElement, TemplateResult } from "lit-element";
import "@material/mwc-button";
import "../../../src/components/ha-card";
import { longPress } from "../../../src/panels/lovelace/common/directives/long-press-directive";
import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive";
import { ActionHandlerEvent } from "../../../src/data/lovelace";
export class DemoUtilLongPress extends LitElement {
protected render(): TemplateResult | void {
protected render(): TemplateResult {
return html`
${this.renderStyle()}
${[1, 2, 3].map(
() => html`
<ha-card>
<mwc-button
@ha-click="${this._handleTap}"
@ha-hold="${this._handleHold}"
.longPress="${longPress()}"
@action=${this._handleAction}
.actionHandler=${actionHandler({})}
>
(long) press me!
</mwc-button>
@@ -28,12 +28,8 @@ export class DemoUtilLongPress extends LitElement {
`;
}
private _handleTap(ev: Event) {
this._addValue(ev, "tap");
}
private _handleHold(ev: Event) {
this._addValue(ev, "hold");
private _handleAction(ev: ActionHandlerEvent) {
this._addValue(ev, ev.detail.action!);
}
private _addValue(ev: Event, value: string) {

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#2157BC" />
<title>HAGallery</title>
<script type="module" src="<%= latestGalleryJS %>"></script>
<style>
body {
font-family: Roboto, Noto, sans-serif;
margin: 0;
padding: 0;
}
</style>
</head>
<body></body>
</html>

View File

@@ -1,6 +1,7 @@
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const webpackBase = require("../build-scripts/webpack.js");
const { createGalleryConfig } = require("../build-scripts/webpack.js");
const { babelLoaderConfig } = require("../build-scripts/babel.js");
const isProd = process.env.NODE_ENV === "production";
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
@@ -8,71 +9,64 @@ const buildPath = path.resolve(__dirname, "dist");
const publicPath = isProd ? "./" : "http://localhost:8080/";
const latestBuild = true;
module.exports = {
mode: isProd ? "production" : "development",
// Disabled in prod while we make Home Assistant able to serve the right files.
// Was source-map
devtool: isProd ? "none" : "inline-source-map",
entry: "./src/entrypoint.js",
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader",
module.exports = createGalleryConfig({
latestBuild: true,
});
const bla = () => {
const oldExports = {
mode: isProd ? "production" : "development",
// Disabled in prod while we make Home Assistant able to serve the right files.
// Was source-map
devtool: isProd ? "none" : "inline-source-map",
entry: "./src/entrypoint.js",
module: {
rules: [
babelLoaderConfig({ latestBuild }),
{
test: /\.css$/,
use: "raw-loader",
},
{
test: /\.(html)$/,
use: {
loader: "html-loader",
options: {
compilerOptions: latestBuild
? { noEmit: false }
: { target: "es5", noEmit: false },
exportAsEs6Default: true,
},
},
],
},
{
test: /\.css$/,
use: "raw-loader",
},
{
test: /\.(html)$/,
use: {
loader: "html-loader",
options: {
exportAsEs6Default: true,
},
},
},
],
},
optimization: webpackBase.optimization(latestBuild),
plugins: [
new CopyWebpackPlugin([
"public",
{ from: "../public", to: "static" },
{ from: "../build-translations/output", to: "static/translations" },
{
from: "../node_modules/leaflet/dist/leaflet.css",
to: "static/images/leaflet/",
},
{
from: "../node_modules/roboto-fontface/fonts/roboto/*.woff2",
to: "static/fonts/roboto/",
},
{
from: "../node_modules/leaflet/dist/images",
to: "static/images/leaflet/",
},
]),
].filter(Boolean),
resolve: webpackBase.resolve,
output: {
filename: "[name].js",
chunkFilename: chunkFilename,
path: buildPath,
publicPath,
},
devServer: {
contentBase: "./public",
},
],
},
optimization: webpackBase.optimization(latestBuild),
plugins: [
new CopyWebpackPlugin([
"public",
{ from: "../public", to: "static" },
{ from: "../build-translations/output", to: "static/translations" },
{
from: "../node_modules/leaflet/dist/leaflet.css",
to: "static/images/leaflet/",
},
{
from: "../node_modules/roboto-fontface/fonts/roboto/*.woff2",
to: "static/fonts/roboto/",
},
{
from: "../node_modules/leaflet/dist/images",
to: "static/images/leaflet/",
},
]),
].filter(Boolean),
resolve: webpackBase.resolve,
output: {
filename: "[name].js",
chunkFilename: chunkFilename,
path: buildPath,
publicPath,
},
devServer: {
contentBase: "./public",
},
};
};

View File

@@ -3,6 +3,7 @@ const path = require("path");
module.exports = {
// Target directory for the build.
buildDir: path.resolve(__dirname, "build"),
nodeDir: path.resolve(__dirname, "../node_modules"),
// Path where the Hass.io frontend will be publicly available.
publicPath: "/api/hassio/app",
};

View File

@@ -4,11 +4,6 @@
# Stop on errors
set -e
cd "$(dirname "$0")/.."
cd "$(dirname "$0")/../.."
OUTPUT_DIR=build
rm -rf $OUTPUT_DIR
node script/gen-icons.js
NODE_ENV=production CI=false ../node_modules/.bin/webpack -p --config webpack.config.js
./node_modules/.bin/gulp build-hassio

View File

@@ -4,11 +4,6 @@
# Stop on errors
set -e
cd "$(dirname "$0")/.."
cd "$(dirname "$0")/../.."
OUTPUT_DIR=build
rm -rf $OUTPUT_DIR
mkdir $OUTPUT_DIR
node script/gen-icons.js
../node_modules/.bin/webpack --watch --progress
./node_modules/.bin/gulp develop-hassio

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env node
const fs = require("fs");
const {
findIcons,
generateIconset,
genMDIIcons,
} = require("../../build-scripts/gulp/gen-icons.js");
function genHassioIcons() {
const iconNames = findIcons("./src", "hassio");
for (const item of findIcons("../src", "hassio")) {
iconNames.add(item);
}
fs.writeFileSync("./hassio-icons.html", generateIconset("hassio", iconNames));
}
genMDIIcons();
genHassioIcons();

View File

@@ -15,7 +15,7 @@ import { HomeAssistant } from "../../../src/types";
import {
HassioAddonInfo,
HassioAddonRepository,
} from "../../../src/data/hassio";
} from "../../../src/data/hassio/addon";
import { navigate } from "../../../src/common/navigate";
import { filterAndSort } from "../components/hassio-filter-addons";
@@ -36,84 +36,90 @@ class HassioAddonRepositoryEl extends LitElement {
}
);
protected render(): TemplateResult | void {
protected render(): TemplateResult {
const repo = this.repo;
const addons = this._getAddons(this.addons, this.filter);
const ha105pluss = this._computeHA105plus;
if (this.filter && addons.length < 1) {
return html`
<div class="card-group">
<div class="title">
<div class="description">
No results found in "${repo.name}"
</div>
</div>
<div class="content">
<p class="description">
No results found in "${repo.name}"
</p>
</div>
`;
}
return html`
<div class="card-group">
<div class="title">
<div class="content">
<h1>
${repo.name}
<div class="description">
Maintained by ${repo.maintainer}<br />
<a class="repo" href=${repo.url} target="_blank">${repo.url}</a>
</div>
</h1>
<p class="description">
Maintained by ${repo.maintainer}<br />
<a class="repo" href=${repo.url} target="_blank">${repo.url}</a>
</p>
<div class="card-group">
${addons.map(
(addon) => html`
<paper-card
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this._addonTapped}
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${addon.name}
.description=${addon.description}
.available=${addon.available}
.icon=${addon.installed && addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle"}
.iconTitle=${addon.installed
? addon.installed !== addon.version
? "New version available"
: "Add-on is installed"
: addon.available
? "Add-on is not installed"
: "Add-on is not available on your system"}
.iconClass=${addon.installed
? addon.installed !== addon.version
? "update"
: "installed"
: !addon.available
? "not_available"
: ""}
.iconImage=${ha105pluss && addon.icon
? `/api/hassio/addons/${addon.slug}/icon`
: undefined}
.showTopbar=${addon.installed || !addon.available}
.topbarClass=${addon.installed
? addon.installed !== addon.version
? "update"
: "installed"
: !addon.available
? "unavailable"
: ""}
></hassio-card-content>
</div>
</paper-card>
`
)}
</div>
${addons.map(
(addon) => html`
<paper-card
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this.addonTapped}
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${addon.name}
.description=${addon.description}
.available=${addon.available}
.icon=${this.computeIcon(addon)}
.iconTitle=${this.computeIconTitle(addon)}
.iconClass=${this.computeIconClass(addon)}
></hassio-card-content>
</div>
</paper-card>
`
)}
</div>
`;
}
private computeIcon(addon) {
return addon.installed && addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle";
}
private computeIconTitle(addon) {
if (addon.installed) {
return addon.installed !== addon.version
? "New version available"
: "Add-on is installed";
}
return addon.available
? "Add-on is not installed"
: "Add-on is not available on your system";
}
private computeIconClass(addon) {
if (addon.installed) {
return addon.installed !== addon.version ? "update" : "installed";
}
return !addon.available ? "not_available" : "";
}
private addonTapped(ev) {
private _addonTapped(ev) {
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}`);
}
private get _computeHA105plus(): boolean {
const [major, minor] = this.hass.config.version.split(".", 2);
return Number(major) > 0 || (major === "0" && Number(minor) >= 105);
}
static get styles(): CSSResultArray {
return [
hassioStyle,

View File

@@ -14,7 +14,7 @@ import {
HassioAddonInfo,
fetchHassioAddonsInfo,
reloadHassioAddons,
} from "../../../src/data/hassio";
} from "../../../src/data/hassio/addon";
import "../../../src/layouts/loading-screen";
import "../components/hassio-search-input";
@@ -48,7 +48,7 @@ class HassioAddonStore extends LitElement {
await this._loadData();
}
protected render(): TemplateResult | void {
protected render(): TemplateResult {
if (!this._addons || !this._repos) {
return html`
<loading-screen></loading-screen>

View File

@@ -17,7 +17,7 @@ import "../../../src/components/buttons/ha-call-api-button";
import "../components/hassio-card-content";
import { hassioStyle } from "../resources/hassio-style";
import { HomeAssistant } from "../../../src/types";
import { HassioAddonRepository } from "../../../src/data/hassio";
import { HassioAddonRepository } from "../../../src/data/hassio/addon";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { repeat } from "lit-html/directives/repeat";
@@ -33,64 +33,66 @@ class HassioRepositoriesEditor extends LitElement {
.sort((a, b) => (a.name < b.name ? -1 : 1))
);
protected render(): TemplateResult | void {
protected render(): TemplateResult {
const repos = this._sortedRepos(this.repos);
return html`
<div class="card-group">
<div class="title">
<div class="content">
<h1>
Repositories
<div class="description">
Configure which add-on repositories to fetch data from:
</div>
</div>
${// Use repeat so that the fade-out from call-service-api-button
// stays with the correct repo after we add/delete one.
repeat(
repos,
(repo) => repo.slug,
(repo) => html`
<paper-card>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${repo.name}
.description=${repo.url}
icon="hassio:github-circle"
></hassio-card-content>
</div>
<div class="card-actions">
<ha-call-api-button
path="hassio/supervisor/options"
.hass=${this.hass}
.data=${this.computeRemoveRepoData(repos, repo.url)}
class="warning"
>
Remove
</ha-call-api-button>
</div>
</paper-card>
`
)}
</h1>
<p class="description">
Configure which add-on repositories to fetch data from:
</p>
<div class="card-group">
${// Use repeat so that the fade-out from call-service-api-button
// stays with the correct repo after we add/delete one.
repeat(
repos,
(repo) => repo.slug,
(repo) => html`
<paper-card>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${repo.name}
.description=${repo.url}
icon="hassio:github-circle"
></hassio-card-content>
</div>
<div class="card-actions">
<ha-call-api-button
path="hassio/supervisor/options"
.hass=${this.hass}
.data=${this.computeRemoveRepoData(repos, repo.url)}
class="warning"
>
Remove
</ha-call-api-button>
</div>
</paper-card>
`
)}
<paper-card>
<div class="card-content add">
<iron-icon icon="hassio:github-circle"></iron-icon>
<paper-input
label="Add new repository by URL"
.value=${this._repoUrl}
@value-changed=${this._urlChanged}
></paper-input>
</div>
<div class="card-actions">
<ha-call-api-button
path="hassio/supervisor/options"
.hass=${this.hass}
.data=${this.computeAddRepoData(repos, this._repoUrl)}
>
Add
</ha-call-api-button>
</div>
</paper-card>
<paper-card>
<div class="card-content add">
<iron-icon icon="hassio:github-circle"></iron-icon>
<paper-input
label="Add new repository by URL"
.value=${this._repoUrl}
@value-changed=${this._urlChanged}
></paper-input>
</div>
<div class="card-actions">
<ha-call-api-button
path="hassio/supervisor/options"
.hass=${this.hass}
.data=${this.computeAddRepoData(repos, this._repoUrl)}
>
Add
</ha-call-api-button>
</div>
</paper-card>
</div>
</div>
`;
}

View File

@@ -1,142 +0,0 @@
import "web-animations-js/web-animations-next-lite.min";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/resources/ha-style";
import { EventsMixin } from "../../../src/mixins/events-mixin";
class HassioAddonAudio extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style">
:host,
paper-card,
paper-dropdown-menu {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
paper-item {
width: 450px;
}
.card-actions {
text-align: right;
}
</style>
<paper-card heading="Audio">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
<paper-dropdown-menu label="Input">
<paper-listbox
slot="dropdown-content"
attr-for-selected="device"
selected="{{selectedInput}}"
>
<template is="dom-repeat" items="[[inputDevices]]">
<paper-item device\$="[[item.device]]"
>[[item.name]]</paper-item
>
</template>
</paper-listbox>
</paper-dropdown-menu>
<paper-dropdown-menu label="Output">
<paper-listbox
slot="dropdown-content"
attr-for-selected="device"
selected="{{selectedOutput}}"
>
<template is="dom-repeat" items="[[outputDevices]]">
<paper-item device\$="[[item.device]]"
>[[item.name]]</paper-item
>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="card-actions">
<mwc-button on-click="_saveSettings">Save</mwc-button>
</div>
</paper-card>
`;
}
static get properties() {
return {
hass: Object,
addon: {
type: Object,
observer: "addonChanged",
},
inputDevices: Array,
outputDevices: Array,
selectedInput: String,
selectedOutput: String,
error: String,
};
}
addonChanged(addon) {
this.setProperties({
selectedInput: addon.audio_input || "null",
selectedOutput: addon.audio_output || "null",
});
if (this.outputDevices) return;
const noDevice = [{ device: "null", name: "-" }];
this.hass.callApi("get", "hassio/hardware/audio").then(
(resp) => {
const dev = resp.data.audio;
const input = Object.keys(dev.input).map((key) => ({
device: key,
name: dev.input[key],
}));
const output = Object.keys(dev.output).map((key) => ({
device: key,
name: dev.output[key],
}));
this.setProperties({
inputDevices: noDevice.concat(input),
outputDevices: noDevice.concat(output),
});
},
() => {
this.setProperties({
inputDevices: noDevice,
outputDevices: noDevice,
});
}
);
}
_saveSettings() {
this.error = null;
const path = `hassio/addons/${this.addon.slug}/options`;
this.hass
.callApi("post", path, {
audio_input: this.selectedInput === "null" ? null : this.selectedInput,
audio_output:
this.selectedOutput === "null" ? null : this.selectedOutput,
})
.then(
() => {
this.fire("hass-api-called", { success: true, path: path });
},
(resp) => {
this.error = resp.body.message;
}
);
}
}
customElements.define("hassio-addon-audio", HassioAddonAudio);

View File

@@ -0,0 +1,186 @@
import "web-animations-js/web-animations-next-lite.min";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../src/types";
import {
HassioAddonDetails,
setHassioAddonOption,
HassioAddonSetOptionParams,
} from "../../../src/data/hassio/addon";
import {
HassioHardwareAudioDevice,
fetchHassioHardwareAudio,
} from "../../../src/data/hassio/hardware";
import { hassioStyle } from "../resources/hassio-style";
import { haStyle } from "../../../src/resources/styles";
@customElement("hassio-addon-audio")
class HassioAddonAudio extends LitElement {
@property() public hass!: HomeAssistant;
@property() public addon!: HassioAddonDetails;
@property() private _error?: string;
@property() private _inputDevices?: HassioHardwareAudioDevice[];
@property() private _outputDevices?: HassioHardwareAudioDevice[];
@property() private _selectedInput!: null | string;
@property() private _selectedOutput!: null | string;
protected render(): TemplateResult {
return html`
<paper-card heading="Audio">
<div class="card-content">
${this._error
? html`
<div class="errors">${this._error}</div>
`
: ""}
<paper-dropdown-menu
label="Input"
@iron-select=${this._setInputDevice}
>
<paper-listbox
slot="dropdown-content"
attr-for-selected="device"
.selected=${this._selectedInput}
>
${this._inputDevices &&
this._inputDevices.map((item) => {
return html`
<paper-item device=${item.device || ""}
>${item.name}</paper-item
>
`;
})}
</paper-listbox>
</paper-dropdown-menu>
<paper-dropdown-menu
label="Output"
@iron-select=${this._setOutputDevice}
>
<paper-listbox
slot="dropdown-content"
attr-for-selected="device"
.selected=${this._selectedOutput}
>
${this._outputDevices &&
this._outputDevices.map((item) => {
return html`
<paper-item device=${item.device || ""}
>${item.name}</paper-item
>
`;
})}
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="card-actions">
<mwc-button @click=${this._saveSettings}>Save</mwc-button>
</div>
</paper-card>
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host,
paper-card,
paper-dropdown-menu {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
paper-item {
width: 450px;
}
.card-actions {
text-align: right;
}
`,
];
}
protected update(changedProperties: PropertyValues): void {
super.update(changedProperties);
if (changedProperties.has("addon")) {
this._addonChanged();
}
}
private _setInputDevice(ev): void {
const device = ev.detail.item.getAttribute("device");
this._selectedInput = device || null;
}
private _setOutputDevice(ev): void {
const device = ev.detail.item.getAttribute("device");
this._selectedOutput = device || null;
}
private async _addonChanged(): Promise<void> {
this._selectedInput = this.addon.audio_input;
this._selectedOutput = this.addon.audio_output;
if (this._outputDevices) {
return;
}
const noDevice: HassioHardwareAudioDevice = { device: null, name: "-" };
try {
const { audio } = await fetchHassioHardwareAudio(this.hass);
const input = Object.keys(audio.input).map((key) => ({
device: key,
name: audio.input[key],
}));
const output = Object.keys(audio.output).map((key) => ({
device: key,
name: audio.output[key],
}));
this._inputDevices = [noDevice, ...input];
this._outputDevices = [noDevice, ...output];
} catch {
this._error = "Failed to fetch audio hardware";
this._inputDevices = [noDevice];
this._outputDevices = [noDevice];
}
}
private async _saveSettings(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
audio_input: this._selectedInput || null,
audio_output: this._selectedOutput || null,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
} catch {
this._error = "Failed to set addon audio device";
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-addon-audio": HassioAddonAudio;
}
}

View File

@@ -1,111 +0,0 @@
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/buttons/ha-call-api-button";
class HassioAddonConfig extends PolymerElement {
static get template() {
return html`
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
}
.card-actions {
@apply --layout;
@apply --layout-justified;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: monospace;
}
.syntaxerror {
color: var(--google-red-500);
}
</style>
<paper-card heading="Config">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
<iron-autogrow-textarea
id="config"
value="{{config}}"
></iron-autogrow-textarea>
</div>
<div class="card-actions">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/options"
data="[[resetData]]"
>Reset to defaults</ha-call-api-button
>
<mwc-button on-click="saveTapped" disabled="[[!configParsed]]"
>Save</mwc-button
>
</div>
</paper-card>
`;
}
static get properties() {
return {
hass: Object,
addon: {
type: Object,
observer: "addonChanged",
},
addonSlug: String,
config: {
type: String,
observer: "configChanged",
},
configParsed: Object,
error: String,
resetData: {
type: Object,
value: {
options: null,
},
},
};
}
addonChanged(addon) {
this.config = addon ? JSON.stringify(addon.options, null, 2) : "";
}
configChanged(config) {
try {
this.$.config.classList.remove("syntaxerror");
this.configParsed = JSON.parse(config);
} catch (err) {
this.$.config.classList.add("syntaxerror");
this.configParsed = null;
}
}
saveTapped() {
this.error = null;
this.hass
.callApi("post", `hassio/addons/${this.addonSlug}/options`, {
options: this.configParsed,
})
.catch((resp) => {
this.error = resp.body.message;
});
}
}
customElements.define("hassio-addon-config", HassioAddonConfig);

View File

@@ -0,0 +1,179 @@
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
query,
} from "lit-element";
import { HomeAssistant } from "../../../src/types";
import {
HassioAddonDetails,
setHassioAddonOption,
HassioAddonSetOptionParams,
} from "../../../src/data/hassio/addon";
import { hassioStyle } from "../resources/hassio-style";
import { haStyle } from "../../../src/resources/styles";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-yaml-editor";
// tslint:disable-next-line: no-duplicate-imports
import { HaYamlEditor } from "../../../src/components/ha-yaml-editor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
@customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement {
@property() public hass!: HomeAssistant;
@property() public addon!: HassioAddonDetails;
@property() private _error?: string;
@property({ type: Boolean }) private _configHasChanged = false;
@query("ha-yaml-editor") private _editor!: HaYamlEditor;
protected render(): TemplateResult {
const editor = this._editor;
// If editor not rendered, don't show the error.
const valid = editor ? editor.isValid : true;
return html`
<paper-card heading="Config">
<div class="card-content">
<ha-yaml-editor
@value-changed=${this._configChanged}
></ha-yaml-editor>
${this._error
? html`
<div class="errors">${this._error}</div>
`
: ""}
${valid
? ""
: html`
<div class="errors">Invalid YAML</div>
`}
</div>
<div class="card-actions">
<mwc-button class="warning" @click=${this._resetTapped}>
Reset to defaults
</mwc-button>
<mwc-button
@click=${this._saveTapped}
.disabled=${!this._configHasChanged || !valid}
>
Save
</mwc-button>
</div>
</paper-card>
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
paper-card {
display: block;
}
.card-actions {
display: flex;
justify-content: space-between;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: monospace;
}
.syntaxerror {
color: var(--google-red-500);
}
`,
];
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has("addon")) {
this._editor.setValue(this.addon.options);
}
}
private _configChanged(): void {
this._configHasChanged = true;
this.requestUpdate();
}
private async _resetTapped(): Promise<void> {
const confirmed = await showConfirmationDialog(this, {
title: this.addon.name,
text: "Are you sure you want to reset all your options?",
confirmText: "reset options",
});
if (!confirmed) {
return;
}
this._error = undefined;
const data: HassioAddonSetOptionParams = {
options: null,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to reset addon configuration, ${err.body?.message ||
err}`;
}
}
private async _saveTapped(): Promise<void> {
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);
this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to save addon configuration, ${err.body?.message ||
err}`;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-addon-config": HassioAddonConfig;
}
}

View File

@@ -1,615 +0,0 @@
import "@polymer/iron-icon/iron-icon";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-tooltip/paper-tooltip";
import "@polymer/paper-toggle-button/paper-toggle-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-label-badge";
import "../../../src/components/ha-markdown";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/resources/ha-style";
import { EventsMixin } from "../../../src/mixins/events-mixin";
import { navigate } from "../../../src/common/navigate";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import "../components/hassio-card-content";
const PERMIS_DESC = {
rating: {
title: "Add-on Security Rating",
description:
"Hass.io provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk).",
},
host_network: {
title: "Host Network",
description:
"Add-ons usually run in their own isolated network layer, which prevents them from accessing the network of the host operating system. In some cases, this network isolation can limit add-ons in providing their services and therefore, the isolation can be lifted by the add-on author, giving the add-on full access to the network capabilities of the host machine. This gives the add-on more networking capabilities but lowers the security, hence, the security rating of the add-on will be lowered when this option is used by the add-on.",
},
homeassistant_api: {
title: "Home Assistant API Access",
description:
"This add-on is allowed to access your running Home Assistant instance directly via the Home Assistant API. This mode handles authentication for the add-on as well, which enables an add-on to interact with Home Assistant without the need for additional authentication tokens.",
},
full_access: {
title: "Full Hardware Access",
description:
"This add-on is given full access to the hardware of your system, by request of the add-on author. Access is comparable to the privileged mode in Docker. Since this opens up possible security risks, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
hassio_api: {
title: "Hass.io API Access",
description:
"The add-on was given access to the Hass.io API, by request of the add-on author. By default, the add-on can access general version information of your system. When the add-on requests 'manager' or 'admin' level access to the API, it will gain access to control multiple parts of your Hass.io system. This permission is indicated by this badge and will impact the security score of the addon negatively.",
},
docker_api: {
title: "Full Docker Access",
description:
"The add-on author has requested the add-on to have management access to the Docker instance running on your system. This mode gives the add-on full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
host_pid: {
title: "Host Processes Namespace",
description:
"Usually, the processes the add-on runs, are isolated from all other system processes. The add-on author has requested the add-on to have access to the system processes running on the host system instance, and allow the add-on to spawn processes on the host system as well. This mode gives the add-on full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
apparmor: {
title: "AppArmor",
description:
"AppArmor ('Application Armor') is a Linux kernel security module that restricts add-ons capabilities like network access, raw socket access, and permission to read, write, or execute specific files.\n\nAdd-on authors can provide their security profiles, optimized for the add-on, or request it to be disabled. If AppArmor is disabled, it will raise security risks and therefore, has a negative impact on the security score of the add-on.",
},
auth_api: {
title: "Home Assistant Authentication",
description:
"An add-on can authenticate users against Home Assistant, allowing add-ons to give users the possibility to log into applications running inside add-ons, using their Home Assistant username/password. This badge indicates if the add-on author requests this capability.",
},
ingress: {
title: "Ingress",
description:
"This add-on is using Ingress to embed its interface securely into Home Assistant.",
},
};
class HassioAddonInfo extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
margin-bottom: 16px;
}
paper-card.warning {
background-color: var(--google-red-500);
color: white;
--paper-card-header-color: white;
}
paper-card.warning mwc-button {
color: white !important;
}
.warning {
color: var(--google-red-500);
}
.addon-header {
@apply --paper-font-headline;
}
.light-color {
color: var(--secondary-text-color);
}
.addon-version {
float: right;
font-size: 15px;
vertical-align: middle;
}
.description {
margin-bottom: 16px;
}
.logo img {
max-height: 60px;
margin: 16px 0;
display: block;
}
.state {
display: flex;
margin: 8px 0;
}
.state div {
width: 180px;
display: inline-block;
}
.state iron-icon {
width: 16px;
color: var(--secondary-text-color);
}
paper-toggle-button {
display: inline;
}
iron-icon.running {
color: var(--paper-green-400);
}
iron-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
ha-markdown img {
max-width: 100%;
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
}
.security {
margin-bottom: 16px;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--iron-icon-height: 45px;
}
.protection-enable mwc-button {
--mdc-theme-primary: white;
}
.description a, ha-markdown a {
color: var(--primary-color);
}
</style>
<template is="dom-if" if="[[computeUpdateAvailable(addon)]]">
<paper-card heading="Update available! 🎉">
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[addon.name]] [[addon.last_version]] is available"
description="You are currently running version [[addon.version]]"
icon="hassio:arrow-up-bold-circle"
icon-class="update"
></hassio-card-content>
<template is="dom-if" if="[[!addon.available]]">
<p>This update is no longer compatible with your system.</p>
</template>
</div>
<div class="card-actions">
<ha-call-api-button
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/update"
disabled="[[!addon.available]]"
>
Update
</ha-call-api-button
>
<template is="dom-if" if="[[addon.changelog]]">
<mwc-button on-click="openChangelog">Changelog</mwc-button>
</template>
</div>
</paper-card>
</template>
<template is="dom-if" if="[[!addon.protected]]">
<paper-card heading="Warning: Protection mode is disabled!" class="warning">
<div class="card-content">
Protection mode on this add-on is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this add-on.
</div>
<div class="card-actions protection-enable">
<mwc-button on-click="protectionToggled">Enable Protection mode</mwc-button>
</div>
</div>
</paper-card>
</template>
<paper-card>
<div class="card-content">
<div class="addon-header">
[[addon.name]]
<div class="addon-version light-color">
<template is="dom-if" if="[[addon.version]]">
[[addon.version]]
<template is="dom-if" if="[[isRunning]]">
<iron-icon
title="Add-on is running"
class="running"
icon="hassio:circle"
></iron-icon>
</template>
<template is="dom-if" if="[[!isRunning]]">
<iron-icon
title="Add-on is stopped"
class="stopped"
icon="hassio:circle"
></iron-icon>
</template>
</template>
<template is="dom-if" if="[[!addon.version]]">
[[addon.last_version]]
</template>
</div>
</div>
<div class="description light-color">
[[addon.description]].<br />
Visit
<a href="[[addon.url]]" target="_blank">[[addon.name]] page</a> for
details.
</div>
<template is="dom-if" if="[[addon.logo]]">
<a href="[[addon.url]]" target="_blank" class="logo">
<img src="/api/hassio/addons/[[addonSlug]]/logo" />
</a>
</template>
<div class="security">
<ha-label-badge
class$="[[computeSecurityClassName(addon.rating)]]"
on-click="showMoreInfo"
id="rating"
value="[[addon.rating]]"
label="rating"
description=""
></ha-label-badge>
<template is="dom-if" if="[[addon.host_network]]">
<ha-label-badge
on-click="showMoreInfo"
id="host_network"
icon="hassio:network"
label="host"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.full_access]]">
<ha-label-badge
on-click="showMoreInfo"
id="full_access"
icon="hassio:chip"
label="hardware"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.homeassistant_api]]">
<ha-label-badge
on-click="showMoreInfo"
id="homeassistant_api"
icon="hassio:home-assistant"
label="hass"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[computeHassioApi(addon)]]">
<ha-label-badge
on-click="showMoreInfo"
id="hassio_api"
icon="hassio:home-assistant"
label="hassio"
description="[[addon.hassio_role]]"
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.docker_api]]">
<ha-label-badge
on-click="showMoreInfo"
id="docker_api"
icon="hassio:docker"
label="docker"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.host_pid]]">
<ha-label-badge
on-click="showMoreInfo"
id="host_pid"
icon="hassio:pound"
label="host pid"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.apparmor]]">
<ha-label-badge
on-click="showMoreInfo"
class$="[[computeApparmorClassName(addon.apparmor)]]"
id="apparmor"
icon="hassio:shield"
label="apparmor"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.auth_api]]">
<ha-label-badge
on-click="showMoreInfo"
id="auth_api"
icon="hassio:key"
label="auth"
description=""
></ha-label-badge>
</template>
<template is="dom-if" if="[[addon.ingress]]">
<ha-label-badge
on-click="showMoreInfo"
id="ingress"
icon="hassio:cursor-default-click-outline"
label="ingress"
description=""
></ha-label-badge>
</template>
</div>
<template is="dom-if" if="[[addon.version]]">
<div class="state">
<div>Start on boot</div>
<paper-toggle-button
on-change="startOnBootToggled"
checked="[[computeStartOnBoot(addon.boot)]]"
></paper-toggle-button>
</div>
<div class="state">
<div>Auto update</div>
<paper-toggle-button
on-change="autoUpdateToggled"
checked="[[addon.auto_update]]"
></paper-toggle-button>
</div>
<template is="dom-if" if="[[addon.ingress]]">
<div class="state">
<div>Show in sidebar</div>
<paper-toggle-button
on-change="panelToggled"
checked="[[addon.ingress_panel]]"
disabled="[[_computeCannotIngressSidebar(hass, addon)]]"
></paper-toggle-button>
<template is="dom-if" if="[[_computeCannotIngressSidebar(hass, addon)]]">
<span>This option requires Home Assistant 0.92 or later.</span>
</template>
</div>
</template>
<div class="state">
<div>
Protection mode
<span>
<iron-icon icon="hassio:information"></iron-icon>
<paper-tooltip>Grant the add-on elevated system access.</paper-tooltip>
</span>
</div>
<paper-toggle-button
on-change="protectionToggled"
checked="[[addon.protected]]"
></paper-toggle-button>
</div>
</template>
</div>
<div class="card-actions">
<template is="dom-if" if="[[addon.version]]">
<mwc-button class="warning" on-click="_unistallClicked"
>Uninstall</mwc-button
>
<template is="dom-if" if="[[addon.build]]">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/rebuild"
>Rebuild</ha-call-api-button
>
</template>
<template is="dom-if" if="[[isRunning]]">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/restart"
>Restart</ha-call-api-button
>
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/stop"
>Stop</ha-call-api-button
>
</template>
<template is="dom-if" if="[[!isRunning]]">
<ha-call-api-button
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/start"
>Start</ha-call-api-button
>
</template>
<template
is="dom-if"
if="[[computeShowWebUI(addon.ingress, addon.webui, isRunning)]]"
>
<a
href="[[pathWebui(addon.webui)]]"
tabindex="-1"
target="_blank"
class="right"
><mwc-button>Open web UI</mwc-button></a
>
</template>
<template
is="dom-if"
if="[[computeShowIngressUI(addon.ingress, isRunning)]]"
>
<mwc-button
tabindex="-1"
class="right"
on-click="openIngress"
>Open web UI</mwc-button>
</template>
</template>
<template is="dom-if" if="[[!addon.version]]">
<template is="dom-if" if="[[!addon.available]]">
<p class="warning">This add-on is not available on your system.</p>
</template>
<ha-call-api-button
disabled="[[!addon.available]]"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/install"
>Install</ha-call-api-button
>
</template>
</div>
</paper-card>
<template is="dom-if" if="[[addon.long_description]]">
<paper-card>
<div class="card-content">
<ha-markdown content="[[addon.long_description]]"></ha-markdown>
</div>
</paper-card>
</template>
`;
}
static get properties() {
return {
hass: Object,
addon: Object,
addonSlug: String,
isRunning: { type: Boolean, computed: "computeIsRunning(addon)" },
};
}
computeIsRunning(addon) {
return addon && addon.state === "started";
}
computeUpdateAvailable(addon) {
return (
addon &&
!addon.detached &&
addon.version &&
addon.version !== addon.last_version
);
}
computeHassioApi(addon) {
return (
addon.hassio_api &&
(addon.hassio_role === "manager" || addon.hassio_role === "admin")
);
}
computeApparmorClassName(apparmor) {
if (apparmor === "profile") {
return "green";
}
if (apparmor === "disable") {
return "red";
}
return "";
}
pathWebui(webui) {
return webui && webui.replace("[HOST]", document.location.hostname);
}
computeShowWebUI(ingress, webui, isRunning) {
return !ingress && webui && isRunning;
}
openIngress() {
navigate(this, `/hassio/ingress/${this.addon.slug}`);
}
computeShowIngressUI(ingress, isRunning) {
return ingress && isRunning;
}
computeStartOnBoot(state) {
return state === "auto";
}
computeSecurityClassName(rating) {
if (rating > 4) {
return "green";
}
if (rating > 2) {
return "yellow";
}
return "red";
}
startOnBootToggled() {
const data = { boot: this.addon.boot === "auto" ? "manual" : "auto" };
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
}
autoUpdateToggled() {
const data = { auto_update: !this.addon.auto_update };
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
}
protectionToggled() {
const data = { protected: !this.addon.protected };
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/security`, data);
this.set("addon.protected", !this.addon.protected);
}
panelToggled() {
const data = { ingress_panel: !this.addon.ingress_panel };
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
}
showMoreInfo(e) {
const id = e.target.getAttribute("id");
showHassioMarkdownDialog(this, {
title: PERMIS_DESC[id].title,
content: PERMIS_DESC[id].description,
});
}
openChangelog() {
this.hass
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
.then((resp) => resp, () => "Error getting changelog")
.then((content) => {
showHassioMarkdownDialog(this, {
title: "Changelog",
content: content,
});
});
}
_unistallClicked() {
if (!confirm("Are you sure you want to uninstall this add-on?")) {
return;
}
const path = `hassio/addons/${this.addonSlug}/uninstall`;
const eventData = {
path: path,
};
this.hass
.callApi("post", path)
.then(
(resp) => {
eventData.success = true;
eventData.response = resp;
},
(resp) => {
eventData.success = false;
eventData.response = resp;
}
)
.then(() => {
this.fire("hass-api-called", eventData);
});
}
_computeCannotIngressSidebar(hass, addon) {
return !addon.ingress || !this._computeHA92plus(hass);
}
_computeHA92plus(hass) {
const [major, minor] = hass.config.version.split(".", 2);
return Number(major) > 0 || (major === "0" && Number(minor) >= 92);
}
}
customElements.define("hassio-addon-info", HassioAddonInfo);

View File

@@ -0,0 +1,799 @@
import "@material/mwc-button";
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-label-badge";
import "../../../src/components/ha-markdown";
import "../../../src/components/ha-switch";
import "../components/hassio-card-content";
import { fireEvent } from "../../../src/common/dom/fire_event";
import {
HassioAddonDetails,
HassioAddonSetOptionParams,
HassioAddonSetSecurityParams,
setHassioAddonOption,
setHassioAddonSecurity,
uninstallHassioAddon,
installHassioAddon,
fetchHassioAddonChangelog,
} from "../../../src/data/hassio/addon";
import { hassioStyle } from "../resources/hassio-style";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { navigate } from "../../../src/common/navigate";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
const PERMIS_DESC = {
rating: {
title: "Add-on Security Rating",
description:
"Hass.io provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk).",
},
host_network: {
title: "Host Network",
description:
"Add-ons usually run in their own isolated network layer, which prevents them from accessing the network of the host operating system. In some cases, this network isolation can limit add-ons in providing their services and therefore, the isolation can be lifted by the add-on author, giving the add-on full access to the network capabilities of the host machine. This gives the add-on more networking capabilities but lowers the security, hence, the security rating of the add-on will be lowered when this option is used by the add-on.",
},
homeassistant_api: {
title: "Home Assistant API Access",
description:
"This add-on is allowed to access your running Home Assistant instance directly via the Home Assistant API. This mode handles authentication for the add-on as well, which enables an add-on to interact with Home Assistant without the need for additional authentication tokens.",
},
full_access: {
title: "Full Hardware Access",
description:
"This add-on is given full access to the hardware of your system, by request of the add-on author. Access is comparable to the privileged mode in Docker. Since this opens up possible security risks, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
hassio_api: {
title: "Hass.io API Access",
description:
"The add-on was given access to the Hass.io API, by request of the add-on author. By default, the add-on can access general version information of your system. When the add-on requests 'manager' or 'admin' level access to the API, it will gain access to control multiple parts of your Hass.io system. This permission is indicated by this badge and will impact the security score of the addon negatively.",
},
docker_api: {
title: "Full Docker Access",
description:
"The add-on author has requested the add-on to have management access to the Docker instance running on your system. This mode gives the add-on full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
host_pid: {
title: "Host Processes Namespace",
description:
"Usually, the processes the add-on runs, are isolated from all other system processes. The add-on author has requested the add-on to have access to the system processes running on the host system instance, and allow the add-on to spawn processes on the host system as well. This mode gives the add-on full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on.",
},
apparmor: {
title: "AppArmor",
description:
"AppArmor ('Application Armor') is a Linux kernel security module that restricts add-ons capabilities like network access, raw socket access, and permission to read, write, or execute specific files.\n\nAdd-on authors can provide their security profiles, optimized for the add-on, or request it to be disabled. If AppArmor is disabled, it will raise security risks and therefore, has a negative impact on the security score of the add-on.",
},
auth_api: {
title: "Home Assistant Authentication",
description:
"An add-on can authenticate users against Home Assistant, allowing add-ons to give users the possibility to log into applications running inside add-ons, using their Home Assistant username/password. This badge indicates if the add-on author requests this capability.",
},
ingress: {
title: "Ingress",
description:
"This add-on is using Ingress to embed its interface securely into Home Assistant.",
},
};
@customElement("hassio-addon-info")
class HassioAddonInfo extends LitElement {
@property() public hass!: HomeAssistant;
@property() public addon!: HassioAddonDetails;
@property() private _error?: string;
@property({ type: Boolean }) private _installing = false;
protected render(): TemplateResult {
return html`
${this._computeUpdateAvailable
? html`
<paper-card heading="Update available! 🎉">
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title="${this.addon.name} ${this.addon
.last_version} is available"
.description="You are currently running version ${this.addon
.version}"
icon="hassio:arrow-up-bold-circle"
iconClass="update"
></hassio-card-content>
${!this.addon.available
? html`
<p>
This update is no longer compatible with your system.
</p>
`
: ""}
</div>
<div class="card-actions">
<ha-call-api-button
.hass=${this.hass}
.disabled=${!this.addon.available}
path="hassio/addons/${this.addon.slug}/update"
>
Update
</ha-call-api-button>
${this.addon.changelog
? html`
<mwc-button @click=${this._openChangelog}>
Changelog
</mwc-button>
`
: ""}
</div>
</paper-card>
`
: ""}
${!this.addon.protected
? html`
<paper-card heading="Warning: Protection mode is disabled!" class="warning">
<div class="card-content">
Protection mode on this add-on is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this add-on.
</div>
<div class="card-actions protection-enable">
<mwc-button @click=${this._protectionToggled}>Enable Protection mode</mwc-button>
</div>
</div>
</paper-card>
`
: ""}
<paper-card>
<div class="card-content">
<div class="addon-header">
${this.addon.name}
<div class="addon-version light-color">
${this.addon.version
? html`
${this.addon.version}
${this._computeIsRunning
? html`
<iron-icon
title="Add-on is running"
class="running"
icon="hassio:circle"
></iron-icon>
`
: html`
<iron-icon
title="Add-on is stopped"
class="stopped"
icon="hassio:circle"
></iron-icon>
`}
`
: html`
${this.addon.last_version}
`}
</div>
</div>
<div class="description light-color">
${this.addon.description}.<br />
Visit
<a href="${this.addon.url}" target="_blank">
${this.addon.name} page</a
>
for details.
</div>
${this.addon.logo
? html`
<a href="${this.addon.url}" target="_blank" class="logo">
<img src="/api/hassio/addons/${this.addon.slug}/logo" />
</a>
`
: ""}
<div class="security">
<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"
icon="hassio:network"
label="host"
description=""
></ha-label-badge>
`
: ""}
${this.addon.full_access
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="full_access"
icon="hassio:chip"
label="hardware"
description=""
></ha-label-badge>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="homeassistant_api"
icon="hassio:home-assistant"
label="hass"
description=""
></ha-label-badge>
`
: ""}
${this._computeHassioApi
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="hassio_api"
icon="hassio:home-assistant"
label="hassio"
.description=${this.addon.hassio_role}
></ha-label-badge>
`
: ""}
${this.addon.docker_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="docker_api"
icon="hassio:docker"
label="docker"
description=""
></ha-label-badge>
`
: ""}
${this.addon.host_pid
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_pid"
icon="hassio:pound"
label="host pid"
description=""
></ha-label-badge>
`
: ""}
${this.addon.apparmor
? html`
<ha-label-badge
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
icon="hassio:shield"
label="apparmor"
description=""
></ha-label-badge>
`
: ""}
${this.addon.auth_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="auth_api"
icon="hassio:key"
label="auth"
description=""
></ha-label-badge>
`
: ""}
${this.addon.ingress
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="ingress"
icon="hassio:cursor-default-click-outline"
label="ingress"
description=""
></ha-label-badge>
`
: ""}
</div>
${this.addon.version
? html`
<div class="state">
<div>Start on boot</div>
<ha-switch
@change=${this._startOnBootToggled}
.checked=${this.addon.boot === "auto"}
haptic
></ha-switch>
</div>
<div class="state">
<div>Auto update</div>
<ha-switch
@change=${this._autoUpdateToggled}
.checked=${this.addon.auto_update}
haptic
></ha-switch>
</div>
${this.addon.ingress
? html`
<div class="state">
<div>Show in sidebar</div>
<ha-switch
@change=${this._panelToggled}
.checked=${this.addon.ingress_panel}
.disabled=${this._computeCannotIngressSidebar}
haptic
></ha-switch>
${this._computeCannotIngressSidebar
? html`
<span>
This option requires Home Assistant 0.92 or
later.
</span>
`
: ""}
</div>
`
: ""}
${this._computeUsesProtectedOptions
? html`
<div class="state">
<div>
Protection mode
<span>
<iron-icon icon="hassio:information"></iron-icon>
<paper-tooltip>
Grant the add-on elevated system access.
</paper-tooltip>
</span>
</div>
<ha-switch
@change=${this._protectionToggled}
.checked=${this.addon.protected}
haptic
></ha-switch>
</div>
`
: ""}
`
: ""}
${this._error
? html`
<div class="errors">${this._error}</div>
`
: ""}
</div>
<div class="card-actions">
${this.addon.version
? html`
<mwc-button class="warning" @click=${this._uninstallClicked}>
Uninstall
</mwc-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>
`
: ""}
${this._computeIsRunning
? html`
<ha-call-api-button
class="warning"
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/restart"
>
Restart
</ha-call-api-button>
<ha-call-api-button
class="warning"
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/stop"
>
Stop
</ha-call-api-button>
`
: html`
<ha-call-api-button
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/start"
>
Start
</ha-call-api-button>
`}
${this._computeShowWebUI
? html`
<a
.href=${this._pathWebui}
tabindex="-1"
target="_blank"
class="right"
>
<mwc-button>
Open web UI
</mwc-button>
</a>
`
: ""}
${this._computeShowIngressUI
? html`
<mwc-button class="right" @click=${this._openIngress}>
Open web UI
</mwc-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}
.progress=${this._installing}
@click=${this._installClicked}
>
Install
</ha-progress-button>
`}
</div>
</paper-card>
${this.addon.long_description
? html`
<paper-card>
<div class="card-content">
<ha-markdown
.content=${this.addon.long_description}
></ha-markdown>
</div>
</paper-card>
`
: ""}
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
paper-card {
display: block;
margin-bottom: 16px;
}
paper-card.warning {
background-color: var(--google-red-500);
color: white;
--paper-card-header-color: white;
}
paper-card.warning mwc-button {
--mdc-theme-primary: white !important;
}
.warning {
color: var(--google-red-500);
--mdc-theme-primary: var(--google-red-500);
}
.light-color {
color: var(--secondary-text-color);
}
.addon-header {
font-size: 24px;
color: var(--paper-card-header-color, --primary-text-color);
}
.addon-version {
float: right;
font-size: 15px;
vertical-align: middle;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
.description {
margin-bottom: 16px;
}
.logo img {
max-height: 60px;
margin: 16px 0;
display: block;
}
.state {
display: flex;
margin: 33px 0;
}
.state div {
width: 180px;
display: inline-block;
}
.state iron-icon {
width: 16px;
height: 16px;
color: var(--secondary-text-color);
}
ha-switch {
display: flex;
}
iron-icon.running {
color: var(--paper-green-400);
}
iron-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
ha-markdown img {
max-width: 100%;
}
protection-enable mwc-button {
--mdc-theme-primary: white;
}
.description a,
ha-markdown a {
color: var(--primary-color);
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
}
.security {
margin-bottom: 16px;
}
.card-actions {
display: flow-root;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--iron-icon-height: 45px;
}
`,
];
}
private get _computeHassioApi(): boolean {
return (
this.addon.hassio_api &&
(this.addon.hassio_role === "manager" ||
this.addon.hassio_role === "admin")
);
}
private get _computeApparmorClassName(): string {
if (this.addon.apparmor === "profile") {
return "green";
}
if (this.addon.apparmor === "disable") {
return "red";
}
return "";
}
private _showMoreInfo(ev): void {
const id = ev.target.getAttribute("id");
showHassioMarkdownDialog(this, {
title: PERMIS_DESC[id].title,
content: PERMIS_DESC[id].description,
});
}
private get _computeIsRunning(): boolean {
return this.addon?.state === "started";
}
private get _computeUpdateAvailable(): boolean | "" {
return (
this.addon &&
!this.addon.detached &&
this.addon.version &&
this.addon.version !== this.addon.last_version
);
}
private get _pathWebui(): string | null {
return (
this.addon.webui &&
this.addon.webui.replace("[HOST]", document.location.hostname)
);
}
private get _computeShowWebUI(): boolean | "" | null {
return !this.addon.ingress && this.addon.webui && this._computeIsRunning;
}
private _openIngress(): void {
navigate(this, `/hassio/ingress/${this.addon.slug}`);
}
private get _computeShowIngressUI(): boolean {
return this.addon.ingress && this._computeIsRunning;
}
private get _computeCannotIngressSidebar(): boolean {
return !this.addon.ingress || !this._computeHA92plus;
}
private get _computeUsesProtectedOptions(): boolean {
return (
this.addon.docker_api || this.addon.full_access || this.addon.host_pid
);
}
private get _computeHA92plus(): boolean {
const [major, minor] = this.hass.config.version.split(".", 2);
return Number(major) > 0 || (major === "0" && Number(minor) >= 92);
}
private async _startOnBootToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
boot: this.addon.boot === "auto" ? "manual" : "auto",
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon option, ${err.body?.message || err}`;
}
}
private async _autoUpdateToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
auto_update: !this.addon.auto_update,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon option, ${err.body?.message || err}`;
}
}
private async _protectionToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetSecurityParams = {
protected: !this.addon.protected,
};
try {
await setHassioAddonSecurity(this.hass, this.addon.slug, data);
const eventdata = {
success: true,
response: undefined,
path: "security",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon security option, ${err.body?.message ||
err}`;
}
}
private async _panelToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
ingress_panel: !this.addon.ingress_panel,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon option, ${err.body?.message || err}`;
}
}
private async _openChangelog(): Promise<void> {
this._error = undefined;
try {
const content = await fetchHassioAddonChangelog(
this.hass,
this.addon.slug
);
showHassioMarkdownDialog(this, {
title: "Changelog",
content,
});
} catch (err) {
this._error = `Failed to get addon changelog, ${err.body?.message ||
err}`;
}
}
private async _installClicked(): Promise<void> {
this._error = undefined;
this._installing = true;
try {
await installHassioAddon(this.hass, this.addon.slug);
const eventdata = {
success: true,
response: undefined,
path: "install",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to install addon, ${err.body?.message || err}`;
}
this._installing = false;
}
private async _uninstallClicked(): Promise<void> {
if (!confirm("Are you sure you want to uninstall this add-on?")) {
return;
}
this._error = undefined;
try {
await uninstallHassioAddon(this.hass, this.addon.slug);
const eventdata = {
success: true,
response: undefined,
path: "uninstall",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to uninstall addon, ${err.body?.message || err}`;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-addon-info": HassioAddonInfo;
}
}

View File

@@ -1,66 +0,0 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html";
import "../../../src/resources/ha-style";
class HassioAddonLogs extends PolymerElement {
static get template() {
return html`
<style include="ha-style">
:host,
paper-card {
display: block;
}
pre {
overflow-x: auto;
white-space: pre-wrap;
overflow-wrap: break-word;
}
</style>
${ANSI_HTML_STYLE}
<paper-card heading="Log">
<div class="card-content" id="content"></div>
<div class="card-actions">
<mwc-button on-click="refresh">Refresh</mwc-button>
</div>
</paper-card>
`;
}
static get properties() {
return {
hass: Object,
addonSlug: {
type: String,
observer: "addonSlugChanged",
},
};
}
addonSlugChanged(slug) {
if (!this.hass) {
setTimeout(() => {
this.addonChanged(slug);
}, 0);
return;
}
this.refresh();
}
refresh() {
this.hass
.callApi("get", `hassio/addons/${this.addonSlug}/logs`)
.then((text) => {
while (this.$.content.lastChild) {
this.$.content.removeChild(this.$.content.lastChild);
}
this.$.content.appendChild(parseTextToColoredPre(text));
});
}
}
customElements.define("hassio-addon-logs", HassioAddonLogs);

View File

@@ -0,0 +1,95 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
query,
} from "lit-element";
import { HomeAssistant } from "../../../src/types";
import {
HassioAddonDetails,
fetchHassioAddonLogs,
} from "../../../src/data/hassio/addon";
import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html";
import { hassioStyle } from "../resources/hassio-style";
import { haStyle } from "../../../src/resources/styles";
@customElement("hassio-addon-logs")
class HassioAddonLogs extends LitElement {
@property() public hass!: HomeAssistant;
@property() public addon!: HassioAddonDetails;
@property() private _error?: string;
@query("#content") private _logContent!: any;
public async connectedCallback(): Promise<void> {
super.connectedCallback();
await this._loadData();
}
protected render(): TemplateResult {
return html`
<paper-card heading="Log">
${this._error
? html`
<div class="errors">${this._error}</div>
`
: ""}
<div class="card-content" id="content"></div>
<div class="card-actions">
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
</div>
</paper-card>
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
ANSI_HTML_STYLE,
css`
:host,
paper-card {
display: block;
}
pre {
overflow-x: auto;
white-space: pre-wrap;
overflow-wrap: break-word;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
`,
];
}
private async _loadData(): Promise<void> {
this._error = undefined;
try {
const content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
while (this._logContent.lastChild) {
this._logContent.removeChild(this._logContent.lastChild as Node);
}
this._logContent.appendChild(parseTextToColoredPre(content));
} catch (err) {
this._error = `Failed to get addon logs, ${err.body?.message || err}`;
}
}
private async _refresh(): Promise<void> {
await this._loadData();
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-addon-logs": HassioAddonLogs;
}
}

View File

@@ -1,129 +0,0 @@
import "@polymer/paper-card/paper-card";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/resources/ha-style";
import { EventsMixin } from "../../../src/mixins/events-mixin";
class HassioAddonNetwork extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style">
:host {
display: block;
}
paper-card {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
.card-actions {
@apply --layout;
@apply --layout-justified;
}
</style>
<paper-card heading="Network">
<div class="card-content">
<template is="dom-if" if="[[error]]">
<div class="errors">[[error]]</div>
</template>
<table>
<tbody>
<tr>
<th>Container</th>
<th>Host</th>
<th>Description</th>
</tr>
<template is="dom-repeat" items="[[config]]">
<tr>
<td>[[item.container]]</td>
<td>
<paper-input
placeholder="disabled"
value="{{item.host}}"
no-label-float=""
></paper-input>
</td>
<td>[[item.description]]</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="card-actions">
<ha-call-api-button
class="warning"
hass="[[hass]]"
path="hassio/addons/[[addonSlug]]/options"
data="[[resetData]]"
>Reset to defaults</ha-call-api-button
>
<mwc-button on-click="saveTapped">Save</mwc-button>
</div>
</paper-card>
`;
}
static get properties() {
return {
hass: Object,
addonSlug: String,
config: Object,
addon: {
type: Object,
observer: "addonChanged",
},
error: String,
resetData: {
type: Object,
value: {
network: null,
},
},
};
}
addonChanged(addon) {
if (!addon) return;
const network = addon.network || {};
const description = addon.network_description || {};
const items = Object.keys(network).map((key) => ({
container: key,
host: network[key],
description: description[key],
}));
this.config = items.sort(function(el1, el2) {
return el1.host - el2.host;
});
}
saveTapped() {
this.error = null;
const data = {};
this.config.forEach(function(item) {
data[item.container] = parseInt(item.host);
});
const path = `hassio/addons/${this.addonSlug}/options`;
this.hass
.callApi("post", path, {
network: data,
})
.then(
() => {
this.fire("hass-api-called", { success: true, path: path });
},
(resp) => {
this.error = resp.body.message;
}
);
}
}
customElements.define("hassio-addon-network", HassioAddonNetwork);

View File

@@ -0,0 +1,202 @@
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { HomeAssistant } from "../../../src/types";
import {
HassioAddonDetails,
HassioAddonSetOptionParams,
setHassioAddonOption,
} from "../../../src/data/hassio/addon";
import { hassioStyle } from "../resources/hassio-style";
import { haStyle } from "../../../src/resources/styles";
import { fireEvent } from "../../../src/common/dom/fire_event";
interface NetworkItem {
description: string;
container: string;
host: number | null;
}
interface NetworkItemInput extends PaperInputElement {
container: string;
}
@customElement("hassio-addon-network")
class HassioAddonNetwork extends LitElement {
@property() public hass!: HomeAssistant;
@property() public addon!: HassioAddonDetails;
@property() private _error?: string;
@property() private _config?: NetworkItem[];
public connectedCallback(): void {
super.connectedCallback();
this._setNetworkConfig();
}
protected render(): TemplateResult {
if (!this._config) {
return html``;
}
return html`
<paper-card heading="Network">
<div class="card-content">
${this._error
? html`
<div class="errors">${this._error}</div>
`
: ""}
<table>
<tbody>
<tr>
<th>Container</th>
<th>Host</th>
<th>Description</th>
</tr>
${this._config!.map((item) => {
return html`
<tr>
<td>${item.container}</td>
<td>
<paper-input
@value-changed=${this._configChanged}
placeholder="disabled"
.value=${item.host}
.container=${item.container}
no-label-float
></paper-input>
</td>
<td>${item.description}</td>
</tr>
`;
})}
</tbody>
</table>
</div>
<div class="card-actions">
<mwc-button class="warning" @click=${this._resetTapped}>
Reset to defaults
</mwc-button>
<mwc-button @click=${this._saveTapped}>Save</mwc-button>
</div>
</paper-card>
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
paper-card {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
.card-actions {
display: flex;
justify-content: space-between;
}
`,
];
}
protected update(changedProperties: PropertyValues): void {
super.update(changedProperties);
if (changedProperties.has("addon")) {
this._setNetworkConfig();
}
}
private _setNetworkConfig(): void {
const network = this.addon.network || {};
const description = this.addon.network_description || {};
const items: NetworkItem[] = Object.keys(network).map((key) => {
return {
container: key,
host: network[key],
description: description[key],
};
});
this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1));
}
private async _configChanged(ev: Event): Promise<void> {
const target = ev.target as NetworkItemInput;
this._config!.forEach((item) => {
if (
item.container === target.container &&
item.host !== parseInt(String(target.value), 10)
) {
item.host = target.value ? parseInt(String(target.value), 10) : null;
}
});
}
private async _resetTapped(): Promise<void> {
const data: HassioAddonSetOptionParams = {
network: null,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon network configuration, ${err.body
?.message || err}`;
}
}
private async _saveTapped(): Promise<void> {
this._error = undefined;
const networkconfiguration = {};
this._config!.forEach((item) => {
networkconfiguration[item.container] = parseInt(String(item.host), 10);
});
const data: HassioAddonSetOptionParams = {
network: networkconfiguration,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon network configuration, ${err.body
?.message || err}`;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-addon-network": HassioAddonNetwork;
}
}

View File

@@ -1,139 +0,0 @@
import "@polymer/app-layout/app-header-layout/app-header-layout";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "./hassio-addon-audio";
import "./hassio-addon-config";
import "./hassio-addon-info";
import "./hassio-addon-logs";
import "./hassio-addon-network";
class HassioAddonView extends PolymerElement {
static get template() {
return html`
<style>
:host {
color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
}
.content {
padding: 24px 0 32px;
display: flex;
flex-direction: column;
align-items: center;
}
hassio-addon-info,
hassio-addon-network,
hassio-addon-audio,
hassio-addon-config {
margin-bottom: 24px;
width: 600px;
}
hassio-addon-logs {
max-width: calc(100% - 8px);
min-width: 600px;
}
@media only screen and (max-width: 600px) {
hassio-addon-info,
hassio-addon-network,
hassio-addon-audio,
hassio-addon-config,
hassio-addon-logs {
max-width: 100%;
min-width: 100%;
}
}
</style>
<hass-subpage header="Hass.io: add-on details" hassio>
<div class="content">
<hassio-addon-info
hass="[[hass]]"
addon="[[addon]]"
addon-slug="[[addonSlug]]"
></hassio-addon-info>
<template is="dom-if" if="[[addon.version]]">
<hassio-addon-config
hass="[[hass]]"
addon="[[addon]]"
addon-slug="[[addonSlug]]"
></hassio-addon-config>
<template is="dom-if" if="[[addon.audio]]">
<hassio-addon-audio
hass="[[hass]]"
addon="[[addon]]"
></hassio-addon-audio>
</template>
<template is="dom-if" if="[[addon.network]]">
<hassio-addon-network
hass="[[hass]]"
addon="[[addon]]"
addon-slug="[[addonSlug]]"
></hassio-addon-network>
</template>
<hassio-addon-logs
hass="[[hass]]"
addon-slug="[[addonSlug]]"
></hassio-addon-logs>
</template>
</div>
</hass-subpage>
`;
}
static get properties() {
return {
hass: Object,
route: {
type: Object,
observer: "routeDataChanged",
},
addonSlug: {
type: String,
computed: "_computeSlug(route)",
},
addon: Object,
};
}
ready() {
super.ready();
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
}
apiCalled(ev) {
const path = ev.detail.path;
if (!path) return;
if (path.substr(path.lastIndexOf("/") + 1) === "uninstall") {
history.back();
} else {
this.routeDataChanged(this.route);
}
}
routeDataChanged(routeData) {
const addon = routeData.path.substr(1);
this.hass.callApi("get", `hassio/addons/${addon}/info`).then(
(info) => {
this.addon = info.data;
},
() => {
this.addon = null;
}
);
}
_computeSlug(route) {
return route.path.substr(1);
}
}
customElements.define("hassio-addon-view", HassioAddonView);

View File

@@ -0,0 +1,159 @@
import "@polymer/app-layout/app-header-layout/app-header-layout";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-spinner/paper-spinner-lite";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant, Route } from "../../../src/types";
import {
HassioAddonDetails,
fetchHassioAddonInfo,
} from "../../../src/data/hassio/addon";
import { hassioStyle } from "../resources/hassio-style";
import { haStyle } from "../../../src/resources/styles";
import "./hassio-addon-audio";
import "./hassio-addon-config";
import "./hassio-addon-info";
import "./hassio-addon-logs";
import "./hassio-addon-network";
@customElement("hassio-addon-view")
class HassioAddonView extends LitElement {
@property() public hass!: HomeAssistant;
@property() public route!: Route;
@property() public addon?: HassioAddonDetails;
protected render(): TemplateResult {
if (!this.addon) {
return html`
<paper-spinner-lite active></paper-spinner-lite>
`;
}
return html`
<hass-subpage header="Hass.io: add-on details" hassio>
<div class="content">
<hassio-addon-info
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-info>
${this.addon && this.addon.version
? html`
<hassio-addon-config
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-config>
${this.addon.audio
? html`
<hassio-addon-audio
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-audio>
`
: ""}
${this.addon.network
? html`
<hassio-addon-network
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-network>
`
: ""}
<hassio-addon-logs
.hass=${this.hass}
.addon=${this.addon}
></hassio-addon-logs>
`
: ""}
</div>
</hass-subpage>
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
}
.content {
padding: 24px 0 32px;
display: flex;
flex-direction: column;
align-items: center;
}
hassio-addon-info,
hassio-addon-network,
hassio-addon-audio,
hassio-addon-config {
margin-bottom: 24px;
width: 600px;
}
hassio-addon-logs {
max-width: calc(100% - 8px);
min-width: 600px;
}
@media only screen and (max-width: 600px) {
hassio-addon-info,
hassio-addon-network,
hassio-addon-audio,
hassio-addon-config,
hassio-addon-logs {
max-width: 100%;
min-width: 100%;
}
}
`,
];
}
protected async firstUpdated(): Promise<void> {
await this._routeDataChanged(this.route);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
}
private async _apiCalled(ev): Promise<void> {
const path: string = ev.detail.path;
if (!path) {
return;
}
if (path === "uninstall") {
history.back();
} else {
await this._routeDataChanged(this.route);
}
}
private async _routeDataChanged(routeData: Route): Promise<void> {
const addon = routeData.path.substr(1);
try {
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo;
} catch {
this.addon = undefined;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-addon-view": HassioAddonView;
}
}

View File

@@ -1,68 +1,75 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { css } from "lit-element";
export const ANSI_HTML_STYLE = html`
<style>
.bold {
font-weight: bold;
}
.italic {
font-style: italic;
}
.underline {
text-decoration: underline;
}
.strikethrough {
text-decoration: line-through;
}
.underline.strikethrough {
text-decoration: underline line-through;
}
.fg-red {
color: rgb(222, 56, 43);
}
.fg-green {
color: rgb(57, 181, 74);
}
.fg-yellow {
color: rgb(255, 199, 6);
}
.fg-blue {
color: rgb(0, 111, 184);
}
.fg-magenta {
color: rgb(118, 38, 113);
}
.fg-cyan {
color: rgb(44, 181, 233);
}
.fg-white {
color: rgb(204, 204, 204);
}
.bg-black {
background-color: rgb(0, 0, 0);
}
.bg-red {
background-color: rgb(222, 56, 43);
}
.bg-green {
background-color: rgb(57, 181, 74);
}
.bg-yellow {
background-color: rgb(255, 199, 6);
}
.bg-blue {
background-color: rgb(0, 111, 184);
}
.bg-magenta {
background-color: rgb(118, 38, 113);
}
.bg-cyan {
background-color: rgb(44, 181, 233);
}
.bg-white {
background-color: rgb(204, 204, 204);
}
</style>
interface State {
bold: boolean;
italic: boolean;
underline: boolean;
strikethrough: boolean;
foregroundColor: null | string;
backgroundColor: null | string;
}
export const ANSI_HTML_STYLE = css`
.bold {
font-weight: bold;
}
.italic {
font-style: italic;
}
.underline {
text-decoration: underline;
}
.strikethrough {
text-decoration: line-through;
}
.underline.strikethrough {
text-decoration: underline line-through;
}
.fg-red {
color: rgb(222, 56, 43);
}
.fg-green {
color: rgb(57, 181, 74);
}
.fg-yellow {
color: rgb(255, 199, 6);
}
.fg-blue {
color: rgb(0, 111, 184);
}
.fg-magenta {
color: rgb(118, 38, 113);
}
.fg-cyan {
color: rgb(44, 181, 233);
}
.fg-white {
color: rgb(204, 204, 204);
}
.bg-black {
background-color: rgb(0, 0, 0);
}
.bg-red {
background-color: rgb(222, 56, 43);
}
.bg-green {
background-color: rgb(57, 181, 74);
}
.bg-yellow {
background-color: rgb(255, 199, 6);
}
.bg-blue {
background-color: rgb(0, 111, 184);
}
.bg-magenta {
background-color: rgb(118, 38, 113);
}
.bg-cyan {
background-color: rgb(44, 181, 233);
}
.bg-white {
background-color: rgb(204, 204, 204);
}
`;
export function parseTextToColoredPre(text) {
@@ -70,7 +77,7 @@ export function parseTextToColoredPre(text) {
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
let i = 0;
const state = {
const state: State = {
bold: false,
italic: false,
underline: false,
@@ -81,29 +88,42 @@ export function parseTextToColoredPre(text) {
const addSpan = (content) => {
const span = document.createElement("span");
if (state.bold) span.classList.add("bold");
if (state.italic) span.classList.add("italic");
if (state.underline) span.classList.add("underline");
if (state.strikethrough) span.classList.add("strikethrough");
if (state.foregroundColor !== null)
if (state.bold) {
span.classList.add("bold");
}
if (state.italic) {
span.classList.add("italic");
}
if (state.underline) {
span.classList.add("underline");
}
if (state.strikethrough) {
span.classList.add("strikethrough");
}
if (state.foregroundColor !== null) {
span.classList.add(`fg-${state.foregroundColor}`);
if (state.backgroundColor !== null)
}
if (state.backgroundColor !== null) {
span.classList.add(`bg-${state.backgroundColor}`);
}
span.appendChild(document.createTextNode(content));
pre.appendChild(span);
};
/* eslint-disable no-cond-assign */
let match;
// tslint:disable-next-line
while ((match = re.exec(text)) !== null) {
const j = match.index;
const j = match!.index;
addSpan(text.substring(i, j));
i = j + match[0].length;
if (match[1] === undefined) continue;
if (match[1] === undefined) {
continue;
}
match[1].split(";").forEach((colorCode) => {
switch (parseInt(colorCode)) {
match[1].split(";").forEach((colorCode: string) => {
switch (parseInt(colorCode, 10)) {
case 0:
// reset
state.bold = false;

View File

@@ -17,21 +17,40 @@ class HassioCardContent extends LitElement {
@property() public hass!: HomeAssistant;
@property() public title!: string;
@property() public description?: string;
@property({ type: Boolean }) public available?: boolean;
@property({ type: Boolean }) public available: boolean = true;
@property({ type: Boolean }) public showTopbar: boolean = false;
@property() public topbarClass?: string;
@property() public datetime?: string;
@property() public iconTitle?: string;
@property() public iconClass?: string;
@property() public icon = "hass:help-circle";
@property() public iconImage?: string;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
return html`
<iron-icon
class=${this.iconClass}
.icon=${this.icon}
.title=${this.iconTitle}
></iron-icon>
${this.showTopbar
? html`
<div class="topbar ${this.topbarClass}"></div>
`
: ""}
${this.iconImage
? html`
<div class="icon_image ${this.iconClass}">
<img src="${this.iconImage}" title="${this.iconTitle}" />
<div></div>
</div>
`
: html`
<iron-icon
class=${this.iconClass}
.icon=${this.icon}
.title=${this.iconTitle}
></iron-icon>
`}
<div>
<div class="title">${this.title}</div>
<div class="title">
${this.title}
</div>
<div class="addition">
${this.description}
${/* treat as available when undefined */
@@ -53,8 +72,9 @@ class HassioCardContent extends LitElement {
static get styles(): CSSResult {
return css`
iron-icon {
margin-right: 16px;
margin-top: 16px;
margin-right: 24px;
margin-left: 8px;
margin-top: 12px;
float: left;
color: var(--secondary-text-color);
}
@@ -88,6 +108,44 @@ class HassioCardContent extends LitElement {
ha-relative-time {
display: block;
}
.icon_image img {
max-height: 40px;
max-width: 40px;
margin-top: 4px;
margin-right: 16px;
float: left;
}
.icon_image.stopped,
.icon_image.not_available {
filter: grayscale(1);
}
.dot {
position: absolute;
background-color: var(--paper-orange-400);
width: 12px;
height: 12px;
top: 8px;
right: 8px;
border-radius: 50%;
}
.topbar {
position: absolute;
width: 100%;
height: 2px;
top: 0;
left: 0;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.topbar.installed {
background-color: var(--primary-color);
}
.topbar.update {
background-color: var(--accent-color);
}
.topbar.unavailable {
background-color: var(--error-color);
}
`;
}
}

View File

@@ -1,4 +1,4 @@
import { HassioAddonInfo } from "../../../src/data/hassio";
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
import * as Fuse from "fuse.js";
export function filterAndSort(addons: HassioAddonInfo[], filter: string) {

View File

@@ -16,7 +16,7 @@ import "@material/mwc-button";
class HassioSearchInput extends LitElement {
@property() private filter?: string;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
return html`
<div class="search-container">
<paper-input

View File

@@ -1,92 +0,0 @@
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/hassio-card-content";
import "../resources/hassio-style";
import NavigateMixin from "../../../src/mixins/navigate-mixin";
class HassioAddons extends NavigateMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style hassio-style">
paper-card {
cursor: pointer;
}
</style>
<div class="content card-group">
<div class="title">Add-ons</div>
<template is="dom-if" if="[[!addons.length]]">
<paper-card>
<div class="card-content">
You don't have any add-ons installed yet. Head over to
<a href="#" on-click="openStore">the add-on store</a> to get
started!
</div>
</paper-card>
</template>
<template
is="dom-repeat"
items="[[addons]]"
as="addon"
sort="sortAddons"
>
<paper-card on-click="addonTapped">
<div class="card-content">
<hassio-card-content
hass="[[hass]]"
title="[[addon.name]]"
description="[[addon.description]]"
available="[[addon.available]]"
icon="[[computeIcon(addon)]]"
icon-title="[[computeIconTitle(addon)]]"
icon-class="[[computeIconClass(addon)]]"
></hassio-card-content>
</div>
</paper-card>
</template>
</div>
`;
}
static get properties() {
return {
hass: Object,
addons: Array,
};
}
sortAddons(a, b) {
return a.name < b.name ? -1 : 1;
}
computeIcon(addon) {
return addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle";
}
computeIconTitle(addon) {
if (addon.installed !== addon.version) return "New version available";
return addon.state === "started"
? "Add-on is running"
: "Add-on is stopped";
}
computeIconClass(addon) {
if (addon.installed !== addon.version) return "update";
return addon.state === "started" ? "running" : "";
}
addonTapped(ev) {
this.navigate("/hassio/addon/" + ev.model.addon.slug);
ev.target.blur();
}
openStore(ev) {
this.navigate("/hassio/store");
ev.target.blur();
}
}
customElements.define("hassio-addons", HassioAddons);

View File

@@ -0,0 +1,109 @@
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../src/types";
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
import { navigate } from "../../../src/common/navigate";
import { hassioStyle } from "../resources/hassio-style";
import { haStyle } from "../../../src/resources/styles";
import "../components/hassio-card-content";
@customElement("hassio-addons")
class HassioAddons extends LitElement {
@property() public hass!: HomeAssistant;
@property() public addons?: HassioAddonInfo[];
protected render(): TemplateResult {
const [major, minor] = this.hass.config.version.split(".", 2);
const ha105pluss =
Number(major) > 0 || (major === "0" && Number(minor) >= 105);
return html`
<div class="content">
<h1>Add-ons</h1>
<div class="card-group">
${!this.addons
? html`
<paper-card>
<div class="card-content">
You don't have any add-ons installed yet. Head over to
<a href="#" @click=${this._openStore}>the add-on store</a>
to get started!
</div>
</paper-card>
`
: this.addons
.sort((a, b) => (a.name > b.name ? 1 : -1))
.map(
(addon) => html`
<paper-card .addon=${addon} @click=${this._addonTapped}>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${addon.name}
.description=${addon.description}
available
.showTopbar=${addon.installed !== addon.version}
topbarClass="update"
.icon=${addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle"}
.iconTitle=${addon.state !== "started"
? "Add-on is stopped"
: addon.installed !== addon.version
? "New version available"
: "Add-on is running"}
.iconClass=${addon.installed &&
addon.installed !== addon.version
? addon.state === "started"
? "update"
: "update stopped"
: addon.installed && addon.state === "started"
? "running"
: "stopped"}
.iconImage=${ha105pluss && addon.icon
? `/api/hassio/addons/${addon.slug}/icon`
: undefined}
></hassio-card-content>
</div>
</paper-card>
`
)}
</div>
</div>
`;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
paper-card {
cursor: pointer;
}
`,
];
}
private _addonTapped(ev: any): void {
navigate(this, `/hassio/addon/${ev.currentTarget.addon.slug}`);
}
private _openStore(): void {
navigate(this, "/hassio/store");
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-addons": HassioAddons;
}
}

View File

@@ -8,26 +8,31 @@ import {
customElement,
} from "lit-element";
import "./hassio-addons";
import "./hassio-hass-update";
import "./hassio-update";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import {
HassioSupervisorInfo,
HassioHomeAssistantInfo,
} from "../../../src/data/hassio";
} from "../../../src/data/hassio/supervisor";
@customElement("hassio-dashboard")
class HassioDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public supervisorInfo!: HassioSupervisorInfo;
@property() public hassInfo!: HassioHomeAssistantInfo;
@property() public hassOsInfo!: HassioHassOSInfo;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
return html`
<div class="content">
<hassio-hass-update
<hassio-update
.hass=${this.hass}
.hassInfo=${this.hassInfo}
></hassio-hass-update>
.supervisorInfo=${this.supervisorInfo}
.hassOsInfo=${this.hassOsInfo}
></hassio-update>
<hassio-addons
.hass=${this.hass}
.addons=${this.supervisorInfo.addons}
@@ -36,12 +41,15 @@ class HassioDashboard extends LitElement {
`;
}
static get styles(): CSSResult {
return css`
.content {
margin: 0 auto;
}
`;
static get styles(): CSSResult[] {
return [
haStyle,
css`
.content {
margin: 0 auto;
}
`,
];
}
}

View File

@@ -1,96 +0,0 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/buttons/ha-call-api-button";
import "../components/hassio-card-content";
import "../resources/hassio-style";
class HassioHassUpdate extends PolymerElement {
static get template() {
return html`
<style include="ha-style hassio-style">
paper-card {
display: block;
margin-bottom: 32px;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
a {
color: var(--primary-color);
}
</style>
<template is="dom-if" if="[[computeUpdateAvailable(hassInfo)]]">
<div class="content">
<div class="card-group">
<paper-card heading="Update available! 🎉">
<div class="card-content">
Home Assistant [[hassInfo.last_version]] is available and you
are currently running Home Assistant [[hassInfo.version]].
<template is="dom-if" if="[[error]]">
<div class="error">Error: [[error]]</div>
</template>
</div>
<div class="card-actions">
<ha-call-api-button
hass="[[hass]]"
path="hassio/homeassistant/update"
>Update</ha-call-api-button
>
<a
href="[[computeReleaseNotesUrl(hassInfo.version)]]"
target="_blank"
>
<mwc-button>Release notes</mwc-button>
</a>
</div>
</paper-card>
</div>
</div>
</template>
`;
}
static get properties() {
return {
hass: Object,
hassInfo: Object,
error: String,
};
}
ready() {
super.ready();
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
}
apiCalled(ev) {
if (ev.detail.success) {
this.errors = null;
return;
}
const response = ev.detail.response;
if (typeof response.body === "object") {
this.errors = response.body.message || "Unknown error";
} else {
this.errors = response.body;
}
}
computeUpdateAvailable(hassInfo) {
return hassInfo.version !== hassInfo.last_version;
}
computeReleaseNotesUrl(version) {
return `https://${
version.includes("b") ? "rc" : "www"
}.home-assistant.io/latest-release-notes/`;
}
}
customElements.define("hassio-hass-update", HassioHassUpdate);

View File

@@ -0,0 +1,188 @@
import {
LitElement,
TemplateResult,
html,
CSSResult,
css,
property,
customElement,
} from "lit-element";
import "@polymer/iron-icon/iron-icon";
import { HomeAssistant } from "../../../src/types";
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import { hassioStyle } from "../resources/hassio-style";
import { haStyle } from "../../../src/resources/styles";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "../../../src/components/buttons/ha-call-api-button";
import "../components/hassio-card-content";
@customElement("hassio-update")
export class HassioUpdate extends LitElement {
@property() public hass!: HomeAssistant;
@property() public hassInfo: HassioHomeAssistantInfo;
@property() public hassOsInfo?: HassioHassOSInfo;
@property() public supervisorInfo: HassioSupervisorInfo;
@property() private _error?: string;
protected render(): TemplateResult {
const updatesAvailable: number = [
this.hassInfo,
this.supervisorInfo,
this.hassOsInfo,
].filter((value) => {
return (
!!value &&
(value.last_version
? value.version !== value.last_version
: value.version !== value.version_latest)
);
}).length;
if (!updatesAvailable) {
return html``;
}
return html`
<div class="content">
${this._error
? html`
<div class="error">Error: ${this._error}</div>
`
: ""}
<h1>
${updatesAvailable > 1
? "Updates Available 🎉"
: "Update Available 🎉"}
</h1>
<div class="card-group">
${this._renderUpdateCard(
"Home Assistant Core",
this.hassInfo.version,
this.hassInfo.last_version,
"hassio/homeassistant/update",
`https://${
this.hassInfo.last_version.includes("b") ? "rc" : "www"
}.home-assistant.io/latest-release-notes/`,
"hassio:home-assistant"
)}
${this._renderUpdateCard(
"Supervisor",
this.supervisorInfo.version,
this.supervisorInfo.last_version,
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisorInfo.last_version}`
)}
${this.hassOsInfo
? this._renderUpdateCard(
"Operating System",
this.hassOsInfo.version,
this.hassOsInfo.version_latest,
"hassio/hassos/update",
`https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}`
)
: ""}
</div>
</div>
`;
}
private _renderUpdateCard(
name: string,
curVersion: string,
lastVersion: string,
apiPath: string,
releaseNotesUrl: string,
icon?: string
): TemplateResult {
if (lastVersion === curVersion) {
return html``;
}
return html`
<paper-card>
<div class="card-content">
${icon
? html`
<div class="icon">
<iron-icon .icon="${icon}" />
</div>
`
: ""}
<div class="update-heading">${name} ${lastVersion}</div>
<div class="warning">
You are currently running version ${curVersion}
</div>
</div>
<div class="card-actions">
<a href="${releaseNotesUrl}" target="_blank">
<mwc-button>Release notes</mwc-button>
</a>
<ha-call-api-button
.hass=${this.hass}
.path=${apiPath}
@hass-api-called=${this._apiCalled}
>
Update
</ha-call-api-button>
</div>
</paper-card>
`;
}
private _apiCalled(ev) {
if (ev.detail.success) {
this._error = "";
return;
}
const response = ev.detail.response;
typeof response.body === "object"
? (this._error = response.body.message || "Unknown error")
: (this._error = response.body);
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
.icon {
--iron-icon-height: 48px;
--iron-icon-width: 48px;
float: right;
margin: 0 0 2px 10px;
}
.update-heading {
font-size: var(--paper-font-subhead_-_font-size);
font-weight: 500;
margin-bottom: 0.5em;
}
.warning {
color: var(--secondary-text-color);
}
.card-content {
height: calc(100% - 47px);
box-sizing: border-box;
}
.card-actions {
text-align: right;
}
.errors {
color: var(--google-red-500);
padding: 16px;
}
a {
text-decoration: none;
}
`,
];
}
}

View File

@@ -1,20 +1,59 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-icon-button/paper-icon-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../../src/components/ha-markdown";
import "../../../../src/resources/ha-style";
import "../../../../src/components/dialog/ha-paper-dialog";
import { customElement } from "lit-element";
import { PaperDialogElement } from "@polymer/paper-dialog";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
query,
} from "lit-element";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyleDialog } from "../../../../src/resources/styles";
import { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
import "../../../../src/components/dialog/ha-paper-dialog";
import "../../../../src/components/ha-markdown";
@customElement("dialog-hassio-markdown")
class HassioMarkdownDialog extends PolymerElement {
static get template() {
class HassioMarkdownDialog extends LitElement {
@property() public title!: string;
@property() public content!: string;
@query("#dialog") private _dialog!: PaperDialogElement;
public showDialog(params: HassioMarkdownDialogParams) {
this.title = params.title;
this.content = params.content;
this._dialog.open();
}
protected render(): TemplateResult {
return html`
<style include="ha-style-dialog">
<ha-paper-dialog id="dialog" with-backdrop="">
<app-toolbar>
<paper-icon-button
icon="hassio:close"
dialog-dismiss=""
></paper-icon-button>
<div main-title="">${this.title}</div>
</app-toolbar>
<paper-dialog-scrollable>
<ha-markdown .content=${this.content || ""}></ha-markdown>
</paper-dialog-scrollable>
</ha-paper-dialog>
`;
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
hassioStyle,
css`
ha-paper-dialog {
min-width: 350px;
font-size: 14px;
@@ -52,32 +91,8 @@ class HassioMarkdownDialog extends PolymerElement {
background-color: var(--primary-color);
}
}
</style>
<ha-paper-dialog id="dialog" with-backdrop="">
<app-toolbar>
<paper-icon-button
icon="hassio:close"
dialog-dismiss=""
></paper-icon-button>
<div main-title="">[[title]]</div>
</app-toolbar>
<paper-dialog-scrollable>
<ha-markdown content="[[content]]"></ha-markdown>
</paper-dialog-scrollable>
</ha-paper-dialog>
`;
}
static get properties() {
return {
title: String,
content: String,
};
}
public showDialog(params) {
this.setProperties(params);
(this.$.dialog as PaperDialogElement).open();
`,
];
}
}

View File

@@ -12,7 +12,9 @@ export const showHassioMarkdownDialog = (
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-markdown",
dialogImport: () =>
import(/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"),
import(
/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"
),
dialogParams,
});
};

474
hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts Normal file → Executable file
View File

@@ -1,19 +1,33 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@material/mwc-button";
import "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { getSignedPath } from "../../../../src/data/auth";
import "../../../../src/resources/ha-style";
import "../../../../src/components/dialog/ha-paper-dialog";
import { customElement } from "lit-element";
import { PaperDialogElement } from "@polymer/paper-dialog";
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
query,
} from "lit-element";
import {
fetchHassioSnapshotInfo,
HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot";
import { getSignedPath } from "../../../../src/data/auth";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
import { fetchHassioSnapshotInfo } from "../../../../src/data/hassio";
import { haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { PolymerChangedEvent } from "../../../../src/polymer-types";
import "../../../../src/components/dialog/ha-paper-dialog";
const _computeFolders = (folders) => {
const list: Array<{ slug: string; name: string; checked: boolean }> = [];
@@ -45,21 +59,179 @@ const _computeAddons = (addons) => {
}));
};
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog extends PolymerElement {
// Commented out because it breaks Polymer! Kept around for when we migrate
// to Lit. Now just putting ts-ignore everywhere because we need this out.
// Sorry future developer.
// public hass!: HomeAssistant;
// protected error?: string;
// private snapshot?: any;
// private dialogParams?: HassioSnapshotDialogParams;
// private restoreHass!: boolean;
// private snapshotPassword!: string;
interface AddonItem {
slug: string;
name: string;
version: string;
checked: boolean | null | undefined;
}
static get template() {
interface FolderItem {
slug: string;
name: string;
checked: boolean | null | undefined;
}
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _error?: string;
@property() private snapshot?: HassioSnapshotDetail;
@property() private _folders!: FolderItem[];
@property() private _addons!: AddonItem[];
@property() private _dialogParams?: HassioSnapshotDialogParams;
@property() private _snapshotPassword!: string;
@property() private _restoreHass: boolean | null | undefined = true;
@query("#dialog") private _dialog!: PaperDialogElement;
public async showDialog(params: HassioSnapshotDialogParams) {
this.snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._folders = _computeFolders(
this.snapshot.folders
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
this._addons = _computeAddons(
this.snapshot.addons
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
this._dialogParams = params;
try {
this._dialog.open();
} catch {
await this.showDialog(params);
}
}
protected render(): TemplateResult {
if (!this.snapshot) {
return html``;
}
return html`
<style include="ha-style-dialog">
<ha-paper-dialog
id="dialog"
with-backdrop=""
.on-iron-overlay-closed=${this._dialogClosed}
>
<app-toolbar>
<paper-icon-button
icon="hassio:close"
dialog-dismiss=""
></paper-icon-button>
<div main-title="">${this._computeName}</div>
</app-toolbar>
<div class="details">
${this.snapshot.type === "full"
? "Full snapshot"
: "Partial snapshot"}
(${this._computeSize})<br />
${this._formatDatetime(this.snapshot.date)}
</div>
<div>Home Assistant:</div>
<paper-checkbox
.checked=${this._restoreHass}
@change="${(ev: Event) =>
(this._restoreHass = (ev.target as PaperCheckboxElement).checked)}"
>
Home Assistant ${this.snapshot.homeassistant}
</paper-checkbox>
${this._folders.length
? html`
<div>Folders:</div>
<paper-dialog-scrollable class="no-margin-top">
${this._folders.map((item) => {
return html`
<paper-checkbox
.checked=${item.checked}
@change="${(ev: Event) =>
this._updateFolders(
item,
(ev.target as PaperCheckboxElement).checked
)}"
>
${item.name}
</paper-checkbox>
`;
})}
</paper-dialog-scrollable>
`
: ""}
${this._addons.length
? html`
<div>Add-on:</div>
<paper-dialog-scrollable class="no-margin-top">
${this._addons.map((item) => {
return html`
<paper-checkbox
.checked=${item.checked}
@change="${(ev: Event) =>
this._updateAddons(
item,
(ev.target as PaperCheckboxElement).checked
)}"
>
${item.name}
</paper-checkbox>
`;
})}
</paper-dialog-scrollable>
`
: ""}
${this.snapshot.protected
? html`
<paper-input
autofocus=""
label="Password"
type="password"
@value-changed=${this._passwordInput}
.value=${this._snapshotPassword}
></paper-input>
`
: ""}
${this._error
? html`
<p class="error">Error: ${this._error}</p>
`
: ""}
<div>Actions:</div>
<ul class="buttons">
<li>
<mwc-button @click=${this._downloadClicked}>
<iron-icon icon="hassio:download" class="icon"></iron-icon>
Download Snapshot
</mwc-button>
</li>
<li>
<mwc-button @click=${this._partialRestoreClicked}>
<iron-icon icon="hassio:history" class="icon"> </iron-icon>
Restore Selected
</mwc-button>
</li>
${this.snapshot.type === "full"
? html`
<li>
<mwc-button @click=${this._fullRestoreClicked}>
<iron-icon icon="hassio:history" class="icon"> </iron-icon>
Wipe &amp; restore
</mwc-button>
</li>
`
: ""}
<li>
<mwc-button @click=${this._deleteClicked}>
<iron-icon icon="hassio:delete" class="icon warning"> </iron-icon>
<span class="warning">Delete Snapshot</span>
</mwc-button>
</li>
</ul>
</ha-paper-dialog>
`;
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-paper-dialog {
min-width: 350px;
font-size: 14px;
@@ -94,259 +266,172 @@ class HassioSnapshotDialog extends PolymerElement {
.details {
color: var(--secondary-text-color);
}
.download {
color: var(--primary-color);
}
.warning,
.error {
color: var(--google-red-500);
}
</style>
<ha-paper-dialog
id="dialog"
with-backdrop=""
on-iron-overlay-closed="_dialogClosed"
>
<app-toolbar>
<paper-icon-button
icon="hassio:close"
dialog-dismiss=""
></paper-icon-button>
<div main-title="">[[_computeName(snapshot)]]</div>
</app-toolbar>
<div class="details">
[[_computeType(snapshot.type)]] ([[_computeSize(snapshot.size)]])<br />
[[_formatDatetime(snapshot.date)]]
</div>
<div>Home Assistant:</div>
<paper-checkbox checked="{{restoreHass}}">
Home Assistant [[snapshot.homeassistant]]
</paper-checkbox>
<template is="dom-if" if="[[_folders.length]]">
<div>Folders:</div>
<template is="dom-repeat" items="[[_folders]]">
<paper-checkbox checked="{{item.checked}}">
[[item.name]]
</paper-checkbox>
</template>
</template>
<template is="dom-if" if="[[_addons.length]]">
<div>Add-ons:</div>
<paper-dialog-scrollable>
<template is="dom-repeat" items="[[_addons]]" sort="_sortAddons">
<paper-checkbox checked="{{item.checked}}">
[[item.name]] <span class="details">([[item.version]])</span>
</paper-checkbox>
</template>
</paper-dialog-scrollable>
</template>
<template is="dom-if" if="[[snapshot.protected]]">
<paper-input
autofocus=""
label="Password"
type="password"
value="{{snapshotPassword}}"
></paper-input>
</template>
<template is="dom-if" if="[[error]]">
<p class="error">Error: [[error]]</p>
</template>
<div class="buttons">
<paper-icon-button
icon="hassio:delete"
on-click="_deleteClicked"
class="warning"
title="Delete snapshot"
></paper-icon-button>
<paper-icon-button
on-click="_downloadClicked"
icon="hassio:download"
class="download"
title="Download snapshot"
></paper-icon-button>
<mwc-button on-click="_partialRestoreClicked"
>Restore selected</mwc-button
>
<template is="dom-if" if="[[_isFullSnapshot(snapshot.type)]]">
<mwc-button on-click="_fullRestoreClicked"
>Wipe &amp; restore</mwc-button
>
</template>
</div>
</ha-paper-dialog>
`;
.buttons {
display: flex;
flex-direction: column;
}
.buttons li {
list-style-type: none;
}
.buttons .icon {
margin-right: 16px;
}
.no-margin-top {
margin-top: 0;
}
`,
];
}
static get properties() {
return {
hass: Object,
dialogParams: Object,
snapshot: Object,
_folders: Object,
_addons: Object,
restoreHass: {
type: Boolean,
value: true,
},
snapshotPassword: String,
error: String,
};
}
public async showDialog(params: HassioSnapshotDialogParams) {
// @ts-ignore
const snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this.setProperties({
dialogParams: params,
snapshot,
_folders: _computeFolders(snapshot.folders),
_addons: _computeAddons(snapshot.addons),
private _updateFolders(item: FolderItem, value: boolean | null | undefined) {
this._folders = this._folders.map((folder) => {
if (folder.slug === item.slug) {
folder.checked = value;
}
return folder;
});
(this.$.dialog as PaperDialogElement).open();
}
protected _isFullSnapshot(type) {
return type === "full";
private _updateAddons(item: AddonItem, value: boolean | null | undefined) {
this._addons = this._addons.map((addon) => {
if (addon.slug === item.slug) {
addon.checked = value;
}
return addon;
});
}
protected _partialRestoreClicked() {
private _passwordInput(ev: PolymerChangedEvent<string>) {
this._snapshotPassword = ev.detail.value;
}
private _partialRestoreClicked() {
if (!confirm("Are you sure you want to restore this snapshot?")) {
return;
}
// @ts-ignore
const addons = this._addons
.filter((addon) => addon.checked)
.map((addon) => addon.slug);
// @ts-ignore
const folders = this._folders
.filter((folder) => folder.checked)
.map((folder) => folder.slug);
const data = {
// @ts-ignore
homeassistant: this.restoreHass,
const data: {
homeassistant: boolean | null | undefined;
addons: any;
folders: any;
password?: string;
} = {
homeassistant: this._restoreHass,
addons,
folders,
};
// @ts-ignore
if (this.snapshot.protected) {
// @ts-ignore
data.password = this.snapshotPassword;
if (this.snapshot!.protected) {
data.password = this._snapshotPassword;
}
// @ts-ignore
this.hass
.callApi(
"POST",
// @ts-ignore
`hassio/snapshots/${this.dialogParams!.slug}/restore/partial`,
`hassio/snapshots/${this.snapshot!.slug}/restore/partial`,
data
)
.then(
() => {
alert("Snapshot restored!");
(this.$.dialog as PaperDialogElement).close();
this._dialog.close();
},
(error) => {
// @ts-ignore
this.error = error.body.message;
this._error = error.body.message;
}
);
}
protected _fullRestoreClicked() {
private _fullRestoreClicked() {
if (!confirm("Are you sure you want to restore this snapshot?")) {
return;
}
// @ts-ignore
const data = this.snapshot.protected
? {
password:
// @ts-ignore
this.snapshotPassword,
}
const data = this.snapshot!.protected
? { password: this._snapshotPassword }
: undefined;
// @ts-ignore
this.hass
.callApi(
"POST",
// @ts-ignore
`hassio/snapshots/${this.dialogParams!.slug}/restore/full`,
`hassio/snapshots/${this.snapshot!.slug}/restore/full`,
data
)
.then(
() => {
alert("Snapshot restored!");
(this.$.dialog as PaperDialogElement).close();
this._dialog.close();
},
(error) => {
// @ts-ignore
this.error = error.body.message;
this._error = error.body.message;
}
);
}
protected _deleteClicked() {
private _deleteClicked() {
if (!confirm("Are you sure you want to delete this snapshot?")) {
return;
}
// @ts-ignore
this.hass
// @ts-ignore
.callApi("POST", `hassio/snapshots/${this.dialogParams!.slug}/remove`)
.callApi("POST", `hassio/snapshots/${this.snapshot!.slug}/remove`)
.then(
() => {
(this.$.dialog as PaperDialogElement).close();
// @ts-ignore
this.dialogParams!.onDelete();
this._dialog.close();
this._dialogParams!.onDelete();
},
(error) => {
// @ts-ignore
this.error = error.body.message;
this._error = error.body.message;
}
);
}
protected async _downloadClicked() {
let signedPath;
private async _downloadClicked() {
let signedPath: { path: string };
try {
signedPath = await getSignedPath(
// @ts-ignore
this.hass,
// @ts-ignore
`/api/hassio/snapshots/${this.dialogParams!.slug}/download`
`/api/hassio/snapshots/${this.snapshot!.slug}/download`
);
} catch (err) {
alert(`Error: ${err.message}`);
return;
}
// @ts-ignore
const name = this._computeName(this.snapshot).replace(/[^a-z0-9]+/gi, "_");
const name = this._computeName.replace(/[^a-z0-9]+/gi, "_");
const a = document.createElement("a");
a.href = signedPath.path;
a.download = `Hass_io_${name}.tar`;
this.$.dialog.appendChild(a);
this._dialog.appendChild(a);
a.click();
this.$.dialog.removeChild(a);
this._dialog.removeChild(a);
}
protected _computeName(snapshot) {
return snapshot ? snapshot.name || snapshot.slug : "Unnamed snapshot";
private get _computeName() {
return this.snapshot
? this.snapshot.name || this.snapshot.slug
: "Unnamed snapshot";
}
protected _computeType(type) {
return type === "full" ? "Full snapshot" : "Partial snapshot";
private get _computeSize() {
return Math.ceil(this.snapshot!.size * 10) / 10 + " MB";
}
protected _computeSize(size) {
return Math.ceil(size * 10) / 10 + " MB";
}
protected _sortAddons(a, b) {
return a.name < b.name ? -1 : 1;
}
protected _formatDatetime(datetime) {
private _formatDatetime(datetime) {
return new Date(datetime).toLocaleDateString(navigator.language, {
weekday: "long",
year: "numeric",
@@ -357,13 +442,12 @@ class HassioSnapshotDialog extends PolymerElement {
});
}
protected _dialogClosed() {
this.setProperties({
dialogParams: undefined,
snapshot: undefined,
_addons: [],
_folders: [],
});
private _dialogClosed() {
this._dialogParams = undefined;
this.snapshot = undefined;
this._snapshotPassword = "";
this._folders = [];
this._addons = [];
}
}

View File

@@ -12,7 +12,9 @@ export const showHassioSnapshotDialog = (
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot",
dialogImport: () =>
import(/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"),
import(
/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"
),
dialogParams,
});
};

View File

@@ -1,9 +1,12 @@
window.loadES5Adapter().then(() => {
// eslint-disable-next-line
import(/* webpackChunkName: "roboto" */ "../../src/resources/roboto");
// eslint-disable-next-line
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons");
// eslint-disable-next-line
import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
});
const styleEl = document.createElement("style");
styleEl.innerHTML = `
body {

View File

@@ -3,7 +3,7 @@ import { PolymerElement } from "@polymer/polymer";
import "@polymer/paper-icon-button";
import "../../src/resources/ha-style";
import applyThemesOnElement from "../../src/common/dom/apply_themes_on_element";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../src/common/dom/fire_event";
import {
HassRouterPage,
@@ -12,19 +12,28 @@ import {
import { HomeAssistant } from "../../src/types";
import {
fetchHassioSupervisorInfo,
fetchHassioHostInfo,
fetchHassioHomeAssistantInfo,
HassioSupervisorInfo,
HassioHostInfo,
HassioHomeAssistantInfo,
fetchHassioAddonInfo,
createHassioSession,
HassioPanelInfo,
} from "../../src/data/hassio";
} from "../../src/data/hassio/supervisor";
import {
fetchHassioHostInfo,
fetchHassioHassOsInfo,
HassioHostInfo,
HassioHassOSInfo,
} from "../../src/data/hassio/host";
import { fetchHassioAddonInfo } from "../../src/data/hassio/addon";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-pages-with-tabs";
import { navigate } from "../../src/common/navigate";
import {
showAlertDialog,
AlertDialogParams,
} from "../../src/dialogs/generic/show-dialog-box";
// The register callback of the IronA11yKeysBehavior inside paper-icon-button
// is not called, causing _keyBindings to be uninitiliazed for paper-icon-button,
@@ -54,24 +63,33 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
addon: {
tag: "hassio-addon-view",
load: () =>
import(/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"),
import(
/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"
),
},
ingress: {
tag: "hassio-ingress-view",
load: () =>
import(/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"),
import(
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
),
},
},
};
@property() private _supervisorInfo: HassioSupervisorInfo;
@property() private _hostInfo: HassioHostInfo;
@property() private _hassOsInfo?: HassioHassOSInfo;
@property() private _hassInfo: HassioHomeAssistantInfo;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
applyThemesOnElement(this, this.hass.themes, this.hass.selectedTheme, true);
applyThemesOnElement(
this.parentElement,
this.hass.themes,
this.hass.selectedTheme,
true
);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
// Paulus - March 17, 2019
// We went to a single hass-toggle-menu event in HA 0.90. However, the
@@ -97,6 +115,14 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
})
);
// Forward haptic events to parent window.
window.addEventListener("haptic", (ev) => {
// @ts-ignore
fireEvent(window.parent, ev.type, ev.detail, {
bubbles: false,
});
});
makeDialogManager(this, document.body);
}
@@ -113,6 +139,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
supervisorInfo: this._supervisorInfo,
hostInfo: this._hostInfo,
hassInfo: this._hassInfo,
hassOsInfo: this._hassOsInfo,
route,
});
} else {
@@ -121,6 +148,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
el.supervisorInfo = this._supervisorInfo;
el.hostInfo = this._hostInfo;
el.hassInfo = this._hassInfo;
el.hassOsInfo = this._hassOsInfo;
el.route = route;
}
}
@@ -139,28 +167,88 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
this._supervisorInfo = supervisorInfo;
this._hostInfo = hostInfo;
this._hassInfo = hassInfo;
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
}
}
private async _redirectIngress(addonSlug: string) {
// When we trigger a navigation, we sleep to make sure we don't
// show the hassio dashboard before navigating away.
const awaitAlert = async (
alertParams: AlertDialogParams,
action: () => void
) => {
await new Promise((resolve) => {
alertParams.confirm = resolve;
showAlertDialog(this, alertParams);
});
action();
await new Promise((resolve) => setTimeout(resolve, 1000));
};
const createSessionPromise = createHassioSession(this.hass).then(
() => true,
() => false
);
let addon;
try {
const [addon] = await Promise.all([
fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {
throw new Error("Failed to fetch add-on info");
}),
createHassioSession(this.hass).catch(() => {
throw new Error("Failed to create an ingress session");
}),
]);
if (!addon.ingress_url) {
throw new Error("Add-on does not support Ingress");
}
location.assign(addon.ingress_url);
// await a promise that doesn't resolve, so we show the loading screen
// while we load the next page.
await new Promise(() => undefined);
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (err) {
alert(`Unable to open ingress connection `);
await awaitAlert(
{
text: "Unable to fetch add-on info to start Ingress",
title: "Hass.io",
},
() => history.back()
);
return;
}
if (!addon.ingress_url) {
await awaitAlert(
{
text: "Add-on does not support Ingress",
title: addon.name,
},
() => history.back()
);
return;
}
if (addon.state !== "started") {
await awaitAlert(
{
text: "Add-on is not running. Please start it first",
title: addon.name,
},
() => navigate(this, `/hassio/addon/${addon.slug}`, true)
);
return;
}
if (!(await createSessionPromise)) {
await awaitAlert(
{
text: "Unable to create an Ingress session",
title: addon.name,
},
() => history.back()
);
return;
}
location.assign(addon.ingress_url);
// await a promise that doesn't resolve, so we show the loading screen
// while we load the next page.
await new Promise(() => undefined);
}
private _apiCalled(ev) {

View File

@@ -23,11 +23,11 @@ import scrollToTarget from "../../src/common/dom/scroll-to-target";
import { haStyle } from "../../src/resources/styles";
import { HomeAssistant, Route } from "../../src/types";
import { navigate } from "../../src/common/navigate";
import { HassioHostInfo, HassioHassOSInfo } from "../../src/data/hassio/host";
import {
HassioSupervisorInfo,
HassioHostInfo,
HassioHomeAssistantInfo,
} from "../../src/data/hassio";
} from "../../src/data/hassio/supervisor";
const HAS_REFRESH_BUTTON = ["store", "snapshots"];
@@ -39,8 +39,9 @@ class HassioPagesWithTabs extends LitElement {
@property() public supervisorInfo!: HassioSupervisorInfo;
@property() public hostInfo!: HassioHostInfo;
@property() public hassInfo!: HassioHomeAssistantInfo;
@property() public hassOsInfo!: HassioHassOSInfo;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
const page = this._page;
return html`
<app-header-layout has-scrolling-region>
@@ -51,7 +52,7 @@ class HassioPagesWithTabs extends LitElement {
.narrow=${this.narrow}
hassio
></ha-menu-button>
<div main-title>Hass.io</div>
<div main-title>Supervisor</div>
${HAS_REFRESH_BUTTON.includes(page)
? html`
<paper-icon-button
@@ -79,6 +80,7 @@ class HassioPagesWithTabs extends LitElement {
.supervisorInfo=${this.supervisorInfo}
.hostInfo=${this.hostInfo}
.hassInfo=${this.hassInfo}
.hassOsInfo=${this.hassOsInfo}
></hassio-tabs-router>
</app-header-layout>
`;
@@ -121,7 +123,7 @@ class HassioPagesWithTabs extends LitElement {
}
paper-tabs {
margin-left: 12px;
--paper-tabs-selection-bar-color: #fff;
--paper-tabs-selection-bar-color: var(--text-primary-color, #fff);
text-transform: uppercase;
}
`,

View File

@@ -11,11 +11,11 @@ import "./dashboard/hassio-dashboard";
import "./snapshots/hassio-snapshots";
import "./addon-store/hassio-addon-store";
import "./system/hassio-system";
import { HassioHostInfo, HassioHassOSInfo } from "../../src/data/hassio/host";
import {
HassioSupervisorInfo,
HassioHostInfo,
HassioHomeAssistantInfo,
} from "../../src/data/hassio";
} from "../../src/data/hassio/supervisor";
@customElement("hassio-tabs-router")
class HassioTabsRouter extends HassRouterPage {
@@ -23,6 +23,7 @@ class HassioTabsRouter extends HassRouterPage {
@property() public supervisorInfo: HassioSupervisorInfo;
@property() public hostInfo: HassioHostInfo;
@property() public hassInfo: HassioHomeAssistantInfo;
@property() public hassOsInfo!: HassioHassOSInfo;
protected routerOptions: RouterOptions = {
routes: {
@@ -49,12 +50,14 @@ class HassioTabsRouter extends HassRouterPage {
supervisorInfo: this.supervisorInfo,
hostInfo: this.hostInfo,
hassInfo: this.hassInfo,
hassOsInfo: this.hassOsInfo,
});
} else {
el.hass = this.hass;
el.supervisorInfo = this.supervisorInfo;
el.hostInfo = this.hostInfo;
el.hassInfo = this.hassInfo;
el.hassOsInfo = this.hassOsInfo;
}
}
}

View File

@@ -9,11 +9,11 @@ import {
css,
} from "lit-element";
import { HomeAssistant, Route } from "../../../src/types";
import { createHassioSession } from "../../../src/data/hassio/supervisor";
import {
createHassioSession,
HassioAddonDetails,
fetchHassioAddonInfo,
} from "../../../src/data/hassio";
} from "../../../src/data/hassio/addon";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage";
@@ -23,7 +23,7 @@ class HassioIngressView extends LitElement {
@property() public route!: Route;
@property() private _addon?: HassioAddonDetails;
protected render(): TemplateResult | void {
protected render(): TemplateResult {
if (!this._addon) {
return html`
<hass-loading-screen></hass-loading-screen>

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