├── .dockerignore ├── .gitignore ├── CLA.md ├── CODE_OF_CONDUCT.md ├── Docker-raspi └── Dockerfile ├── Dockerfile ├── Gemfile ├── HISTORY.md ├── LICENSE.md ├── README.md ├── assets ├── fonts │ ├── FontAwesome.otf │ ├── climacons-webfont.eot │ ├── climacons-webfont.svg │ ├── climacons-webfont.ttf │ ├── climacons-webfont.woff │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── images │ ├── favicon-192x192.png │ ├── favicon-384x384.png │ ├── favicon-apple-180x180.png │ ├── favicon.ico │ ├── logo.png │ └── water-percent.png ├── javascripts │ ├── application.coffee │ ├── clickablewidget.coffee │ ├── cycleDashboards.coffee │ ├── d3-3.2.8.js │ ├── dashing.gridster.coffee │ ├── eventsource.min.js │ ├── gridster │ │ ├── jquery.gridster.js │ │ └── jquery.leanModal.min.js │ ├── jquery.knob.js │ └── rickshaw-1.4.3.min.js └── stylesheets │ ├── _variables.scss │ ├── application.scss │ ├── climacons-font.css │ ├── font-awesome.css │ └── jquery.gridster.css ├── config.ru ├── dashboards ├── example.erb └── layout.erb ├── dashing_start.sh ├── hadashboard ├── hapush ├── .gitignore ├── hapush.cfg.example └── hapush.py ├── images ├── alarm_panel.png └── dash.png ├── init ├── .gitignore ├── dashing ├── dashing.service ├── docker-compose.yml ├── hapush └── hapush.service ├── jobs ├── heartbeat.rb ├── homeassistant.rb └── news.rb ├── lib ├── .gitignore ├── ha_conf.rb.example └── settings.rb ├── public └── 404.html └── widgets ├── change_page ├── change_page.coffee ├── change_page.html └── change_page.scss ├── clock ├── clock.coffee ├── clock.html └── clock.scss ├── comments ├── comments.coffee ├── comments.html └── comments.scss ├── graph ├── graph.coffee ├── graph.html └── graph.scss ├── haalarmaction ├── haalarmaction.coffee ├── haalarmaction.html └── haalarmaction.scss ├── haalarmdigit ├── haalarmdigit.coffee ├── haalarmdigit.html └── haalarmdigit.scss ├── haalarmstatus ├── haalarmstatus.coffee ├── haalarmstatus.html └── haalarmstatus.scss ├── habinary ├── habinary.coffee ├── habinary.html └── habinary.scss ├── hacover ├── hacover.coffee ├── hacover.html └── hacover.scss ├── hadevicetracker ├── hadevicetracker.coffee ├── hadevicetracker.html └── hadevicetracker.scss ├── hadimmer ├── hadimmer.coffee ├── hadimmer.html └── hadimmer.scss ├── hagarage ├── hagarage.coffee ├── hagarage.html └── hagarage.scss ├── hagroup ├── hagroup.coffee ├── hagroup.html └── hagroup.scss ├── hahumidity ├── hahumidity.coffee ├── hahumidity.html └── hahumidity.scss ├── hahumiditymeter ├── hahumiditymeter.coffee ├── hahumiditymeter.html └── hahumiditymeter.scss ├── hainputboolean ├── hainputboolean.coffee ├── hainputboolean.html └── hainputboolean.scss ├── hainputselect ├── hainputselect.coffee ├── hainputselect.html └── hainputselect.scss ├── halock ├── halock.coffee ├── halock.html └── halock.scss ├── halux ├── halux.coffee ├── halux.html └── halux.scss ├── hamediaplayer ├── hamediaplayer.coffee ├── hamediaplayer.html └── hamediaplayer.scss ├── hameter ├── hameter.coffee ├── hameter.html └── hameter.scss ├── hamode ├── hamode.coffee ├── hamode.html └── hamode.scss ├── hamotion ├── hamotion.coffee ├── hamotion.html └── hamotion.scss ├── hascene ├── hascene.coffee ├── hascene.html └── hascene.scss ├── hascript ├── hascript.coffee ├── hascript.html └── hascript.scss ├── haselectdimmer ├── haselectdimmer.coffee ├── haselectdimmer.html └── haselectdimmer.scss ├── hasensor ├── hasensor.coffee ├── hasensor.html └── hasensor.scss ├── hasetlevel ├── hasetlevel.coffee ├── hasetlevel.html └── hasetlevel.scss ├── haswitch ├── haswitch.coffee ├── haswitch.html └── haswitch.scss ├── hatemp ├── hatemp.coffee ├── hatemp.html └── hatemp.scss ├── haweather ├── haweather.coffee ├── haweather.html └── haweather.scss ├── iframe ├── iframe.coffee ├── iframe.html └── iframe.scss ├── image ├── image.coffee ├── image.html └── image.scss ├── list ├── list.coffee ├── list.html └── list.scss ├── meter ├── meter.coffee ├── meter.html └── meter.scss ├── news ├── news.coffee ├── news.html └── news.scss ├── number ├── number.coffee ├── number.html └── number.scss ├── reload ├── reload.coffee ├── reload.html └── reload.scss └── text ├── text.coffee ├── text.html └── text.scss /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore any dashboards you create 2 | dashboards/* 3 | # But don't ignore these 4 | !dashboards/example.erb 5 | !dashboards/layout.erb 6 | 7 | # Don't include custom CSS 8 | assets/stylesheets/_application_custom.scss 9 | assets/stylesheets/_variables_custom.scss 10 | 11 | # Ignore runtime and environment stuff 12 | history.yml 13 | persistent.db 14 | Gemfile.lock 15 | dashing.pid 16 | .bundle 17 | log/thin.log 18 | 19 | # Virtual environment folder for running hapush 20 | venv* 21 | -------------------------------------------------------------------------------- /CLA.md: -------------------------------------------------------------------------------- 1 | # Contributor License Agreement 2 | 3 | ``` 4 | By making a contribution to this project, I certify that: 5 | 6 | (a) The contribution was created in whole or in part by me and I 7 | have the right to submit it under the Apache 2.0 license; or 8 | 9 | (b) The contribution is based upon previous work that, to the best 10 | of my knowledge, is covered under an appropriate open source 11 | license and I have the right under that license to submit that 12 | work with modifications, whether created in whole or in part 13 | by me, under the Apache 2.0 license; or 14 | 15 | (c) The contribution was provided directly to me by some other 16 | person who certified (a), (b) or (c) and I have not modified 17 | it. 18 | 19 | (d) I understand and agree that this project and the contribution 20 | are public and that a record of the contribution (including all 21 | personal information I submit with it) is maintained indefinitely 22 | and may be redistributed consistent with this project or the open 23 | source license(s) involved. 24 | ``` 25 | 26 | ## Attribution 27 | 28 | The text of this license is available under the [Creative Commons Attribution-ShareAlike 3.0 Unported License](http://creativecommons.org/licenses/by-sa/3.0/). It is based on the Linux [Developer Certificate Of Origin](http://elinux.org/Developer_Certificate_Of_Origin), but is modified to explicitly use the Apache 2.0 license 29 | and not mention sign-off. 30 | 31 | ## Signing 32 | 33 | To sign this CLA you must first submit a pull request to a repository under the Home Assistant organization. 34 | 35 | ## Adoption 36 | 37 | This Contributor License Agreement (CLA) was first announced on January 21st, 2017 in [this][cla-blog] blog post and adopted January 28th, 2017. 38 | 39 | [cla-blog]: https://home-assistant.io/blog/2017/01/21/home-assistant-governance/ 40 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [safety@home-assistant.io][email]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available [here][version]. 72 | 73 | ## Adoption 74 | 75 | This Code of Conduct was first adopted January 21st, 2017 and announced in [this][coc-blog] blog post. 76 | 77 | [homepage]: http://contributor-covenant.org 78 | [version]: http://contributor-covenant.org/version/1/4/ 79 | [email]: mailto:safety@home-assistant.io 80 | [coc-blog]: https://home-assistant.io/blog/2017/01/21/home-assistant-governance/ 81 | -------------------------------------------------------------------------------- /Docker-raspi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hypriot/rpi-ruby 2 | MAINTAINER steffen 3 | 4 | RUN apt-get update \ 5 | && apt-get install -y \ 6 | sqlite \ 7 | nodejs \ 8 | libpq-dev \ 9 | libssl-dev \ 10 | libsqlite3-dev \ 11 | ruby-dev \ 12 | python3 \ 13 | python3-pip \ 14 | && mkdir /app \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | WORKDIR /app 18 | COPY . . 19 | 20 | RUN gem install dashing \ 21 | && gem install bundler \ 22 | && bundle \ 23 | && pip3 install daemonize sseclient configobj \ 24 | && pip3 install --upgrade requests 25 | 26 | 27 | EXPOSE 3030 28 | 29 | VOLUME /app/lib /app/dashboards /app/hapush 30 | 31 | CMD /app/hapush/hapush.py -d /app/hapush/hapush.cfg && dashing start 32 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.2.5 2 | MAINTAINER Marijn Giesen 3 | 4 | RUN apt-get update \ 5 | && apt-get install -y \ 6 | sqlite \ 7 | nodejs \ 8 | libpq-dev \ 9 | libssl-dev \ 10 | libsqlite3-dev \ 11 | ruby-dev \ 12 | python3 \ 13 | python3-pip \ 14 | && mkdir /app \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | WORKDIR /app 18 | COPY . . 19 | 20 | RUN gem install dashing \ 21 | && gem install bundler \ 22 | && bundle \ 23 | && pip3 install daemonize sseclient configobj \ 24 | && pip3 install --upgrade requests 25 | 26 | 27 | EXPOSE 3030 28 | 29 | VOLUME /app/lib /app/dashboards /app/hapush 30 | 31 | CMD /app/hapush/hapush.py -d /app/hapush/hapush.cfg && dashing start 32 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | #ruby "2.3.1" 3 | 4 | gem 'dashing' 5 | gem 'thor' 6 | gem 'nokogiri' 7 | gem 'htmlentities' 8 | 9 | # REST 10 | gem 'unirest' 11 | # JSON 12 | gem 'json' 13 | 14 | # Database 15 | gem 'data_mapper' 16 | 17 | # Development 18 | group :development do 19 | gem 'dm-sqlite-adapter' 20 | end 21 | 22 | # Production 23 | #group :production do 24 | # gem 'dm-postgres-adapter' 25 | #end 26 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 1.9.0 (2016-10-23) 6 | 7 | **Features** 8 | 9 | - New CSS structure that allows easy dashboard wide color customizations contributed by [rogersmj](https://community.home-assistant.io/users/rogersmj/activity) 10 | - Added a favicon 11 | 12 | **Fixes** 13 | 14 | None 15 | 16 | **Breaking Changes** 17 | 18 | None 19 | 20 | 1.8.1 (2016-10-09) 21 | ------------------ 22 | 23 | **Features** 24 | 25 | - Updated weather to use Dark Sky sensors 26 | 27 | **Fixes** 28 | 29 | None 30 | 31 | **Breaking Changes** 32 | 33 | - Weather will not work unless Home Assistant is updated to use the Dark Sky component 34 | 35 | 36 | 1.8 (2016-10-09) 37 | ------------------ 38 | 39 | **Features** 40 | 41 | - Add support for binary sensor contributed by [rogersmj](https://community.home-assistant.io/users/rogersmj/activity) 42 | 43 | **Fixes** 44 | 45 | None 46 | 47 | **Breaking Changes** 48 | 49 | None 50 | 51 | 1.7.5 (2016-18-16) 52 | ------------------ 53 | 54 | **Features** 55 | 56 | None 57 | 58 | **Fixes** 59 | 60 | - Allow gridster to draw widgets larger than 5 columns - fix contributed by [mezz64](https://github.com/mezz64) 61 | 62 | **Breaking Changes** 63 | 64 | None 65 | 66 | ------------------ 67 | 68 | ***Version 1.7.4*** 69 | 70 | - Modify IFrame widget for @jbardi and @robpitera 71 | 72 | ***Version 1.7.3*** 73 | 74 | - Add cover widget 75 | - Garage widget is deprecated in favor of the cover widget and will be removed at some point 76 | - Add location text to device_tracker widget 77 | 78 | ***Version 1.7.2*** 79 | 80 | - Change weather sensor names for 0.27.0 81 | 82 | ***Version 1.71*** 83 | 84 | - Remove decimals introduced by Hasensor text fix 85 | 86 | ***Version 1.7*** 87 | 88 | - Add Docker support contributed by [marijngiesen](https://github.com/marijngiesen) 89 | - Add Raspberry PI Docker support contributed by [snizzleorg](https://community.home-assistant.io/users/snizzleorg/activity) 90 | - Fix Hasensor to allow text fields fix suggested by [splnut](https://community.home-assistant.io/users/splnut/activity) 91 | 92 | ***Version 1.6*** 93 | 94 | - Merge Haalarm widgets contributed by [Soul](https://community.home-assistant.io/users/soul/activity) 95 | - Allow Haweather units to be specified as a parameter 96 | 97 | *Breaking Changes* 98 | 99 | It is now necessary to explicitly specify the units for the weather widget or no units will be shown. 100 | 101 | ***Version 1.5.1*** 102 | 103 | - Fixed an issue with Float conversions on a weather field 104 | 105 | *Changes in behavior* 106 | 107 | `Wind Chill` on the weather widget has been replaced by `Apparent Temperature` which is now passed straight through from the sensor value. 108 | 109 | ***Version 1.5*** 110 | 111 | - Merge Hagroup contributed by [jwl173305361](https://community.home-assistant.io/users/jwl173305361/activity) 112 | - Add background color support for all widgets 113 | 114 | ***Version 1.4*** 115 | 116 | - Addition of Halock contributed by [jwl173305361](https://community.home-assistant.io/users/jwl173305361/activity) 117 | - Addition of Hasensor 118 | - Addition of Hameter 119 | 120 | *Breaking Changes* 121 | 122 | None, however, Hasensor is intended as a replacement for Hatemp, Hahumidity and Halux, which are now deprecared and will be removed in a future release. Similarly, Hameter is intended to replace Hahumiditymeter. 123 | 124 | ***Version 1.3.2*** 125 | 126 | - Script buttons now light up for a configurable period when activated 127 | - In order to accommodate the above change, functionality to run scripts and track the state of an input_select has been broken out into a new widget called `Hamode` 128 | 129 | *Breaking Changes* 130 | 131 | - Hascript no longer has the ability to track and display the state of an input_slelect. If you were using this functionality, change the type of your widget to `Hamode` 132 | 133 | 134 | ***Version 1.3.1*** 135 | 136 | - Scene buttons now light up for a configurable period when activated 137 | 138 | ***Version 1.3*** 139 | 140 | - Merge RSS widget contributed by [KRiS](https://community.home-assistant.io/users/kris/activity) 141 | - Merge Hahumiditymeter contributed by [Shiv Chanders](https://community.home-assistant.io/users/chanders/activity) 142 | - Allow temperature unit to be specified in the dasboard 143 | - Remove main.erb and replace it with example.erb 144 | - Update README to reflect new widgets 145 | - Update README with additional install notes 146 | - Update README with section on updating the dashboard 147 | 148 | *Breaking Changes* 149 | 150 | Previously temperature units defaulted to Fahrenheit - now there is no default, you must explicitly specify it in the Hatemp widget or you will get no units at all. 151 | 152 | ***Version 1.2.1*** 153 | 154 | - Minor typos in README 155 | 156 | ***Version 1.2*** 157 | 158 | - Fix docs and excample cfg to remove scheme from `hapush` dash_host config variable 159 | 160 | ***Version 1.1*** 161 | 162 | - Expand instructions 163 | - Allow no api_key 164 | - Allow http connections 165 | 166 | ***Version 1.0*** 167 | 168 | Initial Release 169 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | ============== 3 | 4 | _Version 2.0, January 2004_ 5 | _<>_ 6 | 7 | ### Terms and Conditions for use, reproduction, and distribution 8 | 9 | #### 1. Definitions 10 | 11 | “License” shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | “Legal Entity” shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, “control” means **(i)** the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the 22 | outstanding shares, or **(iii)** beneficial ownership of such entity. 23 | 24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | “Source” form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | “Object” form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | “Work” shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | “Derivative Works” shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | “Contribution” shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | “submitted” means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as “Not a Contribution.” 58 | 59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | #### 2. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby 66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 67 | irrevocable copyright license to reproduce, prepare Derivative Works of, 68 | publicly display, publicly perform, sublicense, and distribute the Work and such 69 | Derivative Works in Source or Object form. 70 | 71 | #### 3. Grant of Patent License 72 | 73 | Subject to the terms and conditions of this License, each Contributor hereby 74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 75 | irrevocable (except as stated in this section) patent license to make, have 76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 77 | such license applies only to those patent claims licensable by such Contributor 78 | that are necessarily infringed by their Contribution(s) alone or by combination 79 | of their Contribution(s) with the Work to which such Contribution(s) was 80 | submitted. If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this License 84 | for that Work shall terminate as of the date such litigation is filed. 85 | 86 | #### 4. Redistribution 87 | 88 | You may reproduce and distribute copies of the Work or Derivative Works thereof 89 | in any medium, with or without modifications, and in Source or Object form, 90 | provided that You meet the following conditions: 91 | 92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of 93 | this License; and 94 | * **(b)** You must cause any modified files to carry prominent notices stating that You 95 | changed the files; and 96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, 97 | all copyright, patent, trademark, and attribution notices from the Source form 98 | of the Work, excluding those notices that do not pertain to any part of the 99 | Derivative Works; and 100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any 101 | Derivative Works that You distribute must include a readable copy of the 102 | attribution notices contained within such NOTICE file, excluding those notices 103 | that do not pertain to any part of the Derivative Works, in at least one of the 104 | following places: within a NOTICE text file distributed as part of the 105 | Derivative Works; within the Source form or documentation, if provided along 106 | with the Derivative Works; or, within a display generated by the Derivative 107 | Works, if and wherever such third-party notices normally appear. The contents of 108 | the NOTICE file are for informational purposes only and do not modify the 109 | License. You may add Your own attribution notices within Derivative Works that 110 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 111 | provided that such additional attribution notices cannot be construed as 112 | modifying the License. 113 | 114 | You may add Your own copyright statement to Your modifications and may provide 115 | additional or different license terms and conditions for use, reproduction, or 116 | distribution of Your modifications, or for any such Derivative Works as a whole, 117 | provided Your use, reproduction, and distribution of the Work otherwise complies 118 | with the conditions stated in this License. 119 | 120 | #### 5. Submission of Contributions 121 | 122 | Unless You explicitly state otherwise, any Contribution intentionally submitted 123 | for inclusion in the Work by You to the Licensor shall be under the terms and 124 | conditions of this License, without any additional terms or conditions. 125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 126 | any separate license agreement you may have executed with Licensor regarding 127 | such Contributions. 128 | 129 | #### 6. Trademarks 130 | 131 | This License does not grant permission to use the trade names, trademarks, 132 | service marks, or product names of the Licensor, except as required for 133 | reasonable and customary use in describing the origin of the Work and 134 | reproducing the content of the NOTICE file. 135 | 136 | #### 7. Disclaimer of Warranty 137 | 138 | Unless required by applicable law or agreed to in writing, Licensor provides the 139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 141 | including, without limitation, any warranties or conditions of TITLE, 142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 143 | solely responsible for determining the appropriateness of using or 144 | redistributing the Work and assume any risks associated with Your exercise of 145 | permissions under this License. 146 | 147 | #### 8. Limitation of Liability 148 | 149 | In no event and under no legal theory, whether in tort (including negligence), 150 | contract, or otherwise, unless required by applicable law (such as deliberate 151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 152 | liable to You for damages, including any direct, indirect, special, incidental, 153 | or consequential damages of any character arising as a result of this License or 154 | out of the use or inability to use the Work (including but not limited to 155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 156 | any and all other commercial damages or losses), even if such Contributor has 157 | been advised of the possibility of such damages. 158 | 159 | #### 9. Accepting Warranty or Additional Liability 160 | 161 | While redistributing the Work or Derivative Works thereof, You may choose to 162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 163 | other liability obligations and/or rights consistent with this License. However, 164 | in accepting such obligations, You may act only on Your own behalf and on Your 165 | sole responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any liability 167 | incurred by, or claims asserted against, such Contributor by reason of your 168 | accepting any such warranty or additional liability. 169 | 170 | _END OF TERMS AND CONDITIONS_ 171 | 172 | ### APPENDIX: How to apply the Apache License to your work 173 | 174 | To apply the Apache License to your work, attach the following boilerplate 175 | notice, with the fields enclosed by brackets `[]` replaced with your own 176 | identifying information. (Don't include the brackets!) The text should be 177 | enclosed in the appropriate comment syntax for the file format. We also 178 | recommend that a file or class name and description of purpose be included on 179 | the same “printed page” as the copyright notice for easier identification within 180 | third-party archives. 181 | 182 | Copyright [yyyy] [name of copyright owner] 183 | 184 | Licensed under the Apache License, Version 2.0 (the "License"); 185 | you may not use this file except in compliance with the License. 186 | You may obtain a copy of the License at 187 | 188 | http://www.apache.org/licenses/LICENSE-2.0 189 | 190 | Unless required by applicable law or agreed to in writing, software 191 | distributed under the License is distributed on an "AS IS" BASIS, 192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 193 | See the License for the specific language governing permissions and 194 | limitations under the License. 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Please note: This version of HADashboard is now deprecated. It will no longer be developed and there will be no further bugfixes. HADashboard has been completely re-written and is now part of AppDaemon. For further details see the [HADashboard page](https://home-assistant.io/docs/ecosystem/hadashboard/) on the Home Assistant Website. 2 | 3 | For Reference, here is the old content. It should still work, but the new version is much more performant, has additional finctions and is being actively maintained. You have been warned! 4 | 5 | # Description 6 | 7 | HADashboard is a dashboard for [Home Assistant](https://home-assistant.io/) that is intended to be wall mounted, and is optimized for distance viewing. 8 | 9 | ![UI](images/dash.png) 10 | 11 | HADashboard was originally created by the excellent work of [FlorianZ](https://github.com/FlorianZ/hadashboard) for use with the SmartThings Home Automation system, with notable contributions from the [SmartThings Community](https://community.smartthings.com/t/home-automation-dashboard/4926). I would also like to acknowledge contributions made by [zipriddy](https://github.com/zpriddy/SmartThings_PyDash). This is my port of hadashboard to Home Assistant. 12 | 13 | Full installation and configuration instructions are found [here](https://home-assistant.io/ecosystem/hadashboard). 14 | -------------------------------------------------------------------------------- /assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /assets/fonts/climacons-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/fonts/climacons-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/climacons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/fonts/climacons-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/climacons-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/fonts/climacons-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /assets/images/favicon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/images/favicon-192x192.png -------------------------------------------------------------------------------- /assets/images/favicon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/images/favicon-384x384.png -------------------------------------------------------------------------------- /assets/images/favicon-apple-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/images/favicon-apple-180x180.png -------------------------------------------------------------------------------- /assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/images/favicon.ico -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/water-percent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/assets/images/water-percent.png -------------------------------------------------------------------------------- /assets/javascripts/application.coffee: -------------------------------------------------------------------------------- 1 | # Use the Yaffle EventSource polyfill to work around browser issues 2 | #= require eventsource.min.js 3 | 4 | # dashing.js is located in the dashing framework 5 | # It includes jquery & batman for you. 6 | #= require dashing.js 7 | 8 | #= require_directory . 9 | #= require_tree ../../widgets 10 | 11 | console.log("Yeah! The dashboard has started!") 12 | 13 | Dashing.on 'ready', -> 14 | Dashing.widget_margins ||= [5, 5] 15 | Dashing.widget_base_dimensions ||= [120, 120] 16 | Dashing.numColumns ||= 8 17 | 18 | contentWidth = (Dashing.widget_base_dimensions[0] + Dashing.widget_margins[0] * 2) * Dashing.numColumns 19 | 20 | Batman.setImmediate -> 21 | $('.gridster').width(contentWidth) 22 | $('.gridster > ul').gridster 23 | widget_margins: Dashing.widget_margins 24 | widget_base_dimensions: Dashing.widget_base_dimensions 25 | avoid_overlapped_widgets: !Dashing.customGridsterLayout 26 | max_size_x: Dashing.numColumns 27 | draggable: 28 | stop: Dashing.showGridsterInstructions 29 | start: -> Dashing.currentWidgetPositions = Dashing.getWidgetPositions() 30 | if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) 31 | $('.gridster > ul').each -> 32 | $(@).gridster().data('gridster').draggable().disable() 33 | Dashing.cycleDashboards({timeInSeconds: 0, stagger: true, page: 1}) 34 | -------------------------------------------------------------------------------- /assets/javascripts/clickablewidget.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.ClickableWidget extends Dashing.Widget 2 | constructor: -> 3 | super 4 | $(@node).on 'click', (evt) => @handleClick evt 5 | $(@node).on 'touchstart', (evt) => @handleTouchStart evt 6 | $(@node).on 'touchmove', (evt) => @handleTouchMove evt 7 | $(@node).on 'touchend', (evt) => @handleTouchEnd evt 8 | 9 | handleClick: (evt) -> 10 | @onClick evt 11 | 12 | handleTouchStart: (evt) -> 13 | evt.preventDefault() 14 | @onTouchStart evt 15 | 16 | handleTouchMove: (evt) -> 17 | @onTouchMove evt 18 | 19 | handleTouchEnd: (evt) -> 20 | @onTouchEnd evt 21 | @onClick evt 22 | 23 | onClick: (evt) -> 24 | # override for click events 25 | 26 | onTouchStart: (evt) -> 27 | # override for touchstart events 28 | 29 | onTouchMove: (evt) -> 30 | # override for touchmove events 31 | 32 | onTouchEnd: (evt) -> 33 | # override for touchend events 34 | -------------------------------------------------------------------------------- /assets/javascripts/cycleDashboards.coffee: -------------------------------------------------------------------------------- 1 | # Flying Widgets v0.1.1 2 | # 3 | # To use, put this file in assets/javascripts/cycleDashboard.coffee. Then find this line in 4 | # application.coffee: 5 | # 6 | # $('.gridster ul:first').gridster 7 | # 8 | # And change it to: 9 | # 10 | # $('.gridster > ul').gridster 11 | # 12 | # Finally, put multiple gridster divs in your dashboard, and add a call to Dashing.cycleDashboards() 13 | # to the javascript at the top of your dashboard: 14 | # 15 | # 22 | # 23 | # <% content_for :title do %>Loop Dashboard<% end %> 24 | # 25 | #
26 | #
    27 | # 28 | #
  • 29 | #
    30 | #
  • 31 | # 32 | #
33 | #
34 | # 35 | #
36 | #
    37 | # 38 | #
39 | #
40 | # 41 | 42 | # Some generic helper functions 43 | sleep = (timeInSeconds, fn) -> setTimeout fn, timeInSeconds * 1000 44 | isArray = (obj) -> Object.prototype.toString.call(obj) is '[object Array]' 45 | isString = (obj) -> Object.prototype.toString.call(obj) is '[object String]'; 46 | isFunction = (obj) -> obj && obj.constructor && obj.call && obj.apply 47 | 48 | #### Show/Hide functions. 49 | # 50 | # Every member of `showFunctions` and `hideFunctions` must be one of: 51 | # 52 | # * A `{start, end, transition}` object (transition defaults to 'all 1s'.) 53 | # * A `{transitionFunction}` object. 54 | # * A `fn($dashboard, widget, originalLocations)` which returns one of the above. 55 | # 56 | # The easiest way to define a transition is just to specify start and end CSS proprties for each 57 | # widget with a `{start, end}` object. The `fadeOut` and `fadeIn` are some of the simplest 58 | # examples below. Sometimes you might need slightly more control, in which case `start` and 59 | # `end` can each be functions of the form `($widget, index)`, where $widget is the jquery object 60 | # for the widget being transformed, and index is the index of the widget within the dashboard. 61 | # The function form is handy when you want to do something different for each widget, depending 62 | # on it's location. 63 | # 64 | # For even more control, you can specify a `fn($dashboard, widgets, originalLocations)` function 65 | # in place of the entire object. This is handy when you have some setup work to do for your 66 | # transition, such as detecting the width of the page so you can move all widgets off-screen. 67 | # 68 | # For the ultimate in control, you can specify a 69 | # `transitionFunction{$dashboard, options, done}` object. It will be up to you to 70 | # do whatever you need to do in order to hide or display the dashboard. The CSS of every widget 71 | # will be reset to something sane when the function completes, but otherwise it's entirely 72 | # up to you. Params are: 73 | # 74 | # * `$dashboard` - jquery object of the dashboard to show/hide. 75 | # * `options.stagger` - True if transition should be staggered. 76 | # * `options.widgets` - An array of all widgets in the dashboard. 77 | # * `options.originalLocations` - An array of CSS data about the location, opacity, etc... of 78 | # each widget. 79 | # * `done()` - Async callback. Make sure you call this! 80 | # 81 | 82 | hideFunctions = { 83 | # toRight: ($dashboard, widgets, originalLocations) -> 84 | # documentWidth = $(document).width() 85 | # return {end: (($widget) -> {left: documentWidth, opacity: 0})} 86 | 87 | # shrink: { 88 | # start: { 89 | # opacity: 1, 90 | # transform: 'scale(1,1)', 91 | # "-webkit-transform": 'scale(1,1)' 92 | # }, 93 | # end: { 94 | # transform: 'scale(0,0)', 95 | # "-webkit-transform": 'scale(0,0)', 96 | # opacity: 0 97 | # } 98 | # } 99 | 100 | fadeOut: { 101 | start: {opacity: 1} 102 | end: {opacity: 0} 103 | } 104 | 105 | # explode: { 106 | # start: { 107 | # opacity: 1 108 | # transform: 'scale(1,1)', 109 | # "-webkit-transform": 'scale(1,1)' 110 | # } 111 | # end: { 112 | # opacity: 0 113 | # transform: 'scale(2,2)', 114 | # "-webkit-transform": 'scale(2,2)' 115 | # } 116 | # } 117 | } 118 | 119 | # Handy function for reversing simple transitions 120 | reverseTransition = (obj) -> 121 | if isFunction(obj) or obj.transitionFunction? 122 | throw new Error("Can't reverse transition") 123 | return {start: obj.end, end: obj.start, transition: obj.transition} 124 | 125 | showFunctions = { 126 | # fromLeft: ($dashboard, widgets, originalLocations) -> 127 | # start: (($widget, index) -> {left: "#{-$widget.width() - $dashboard.width()}px", opacity: 0}), 128 | # end: (($widget, index) -> originalLocations[index]), 129 | 130 | # fromTop: ($dashboard, widgets, originalLocations) -> 131 | # start: (($widget, index) -> {top: "#{-$widget.height() - $dashboard.height()}px", opacity: 0}), 132 | # end: (($widget, index) -> return originalLocations[index]), 133 | 134 | # zoom: reverseTransition(hideFunctions.shrink) 135 | 136 | fadeIn: reverseTransition(hideFunctions.fadeOut) 137 | 138 | # implode: reverseTransition(hideFunctions.explode) 139 | 140 | } 141 | 142 | # Move an element from one place to another using a CSS3 transition. 143 | # 144 | # * `elements` - One or more elements to move, in an array. 145 | # * `transition` - The transition string to apply (e.g.: 'left 1s ease 0s') 146 | # * `start` - This can be an object (e.g. `{left: 0px}`) or a `fn($el, index)` 147 | # which returns such an object. This is the location the object will start at. 148 | # If start is omitted, then the current location of the object will be used 149 | # as the start. 150 | # * `end` - As with `start`, this can be an object or a function. `end` is required. 151 | # * `timeInSeconds` - The time required to complete the transition. This function will 152 | # wait this long before calling `done()`. 153 | # * `offset` is an offset for the index passed into `start()` and `end()`. Handy when 154 | # you want to split up an array of 155 | # * `done()` - Async callback. 156 | moveWithTransition = (elements, {transition, start, end, timeInSeconds, offset}, done) -> 157 | transition = transition or '' 158 | timeInSeconds = timeInSeconds or 0 159 | end = end or {} 160 | offset = offset or 0 161 | 162 | origTransitions = [] 163 | moveToStart = () -> 164 | for el, index in elements 165 | $el = $(el) 166 | origTransitions[index + offset] = $el.css 'transition' 167 | $el.css transition: 'left 0s ease 0s' 168 | $el.css(if isFunction start then start($el, index + offset) else start) 169 | 170 | moveToEnd = () -> 171 | for el, index in elements 172 | $el = $(el) 173 | $el.css transition: transition 174 | $el.css(if isFunction end then end($el, index + offset) else end) 175 | sleep Math.max(0, timeInSeconds), -> 176 | $el.css transition: origTransitions[index + offset] 177 | done? null 178 | 179 | if start 180 | moveToStart() 181 | sleep 0, -> moveToEnd() 182 | else 183 | moveToEnd() 184 | 185 | # Runs a function which shows or hides the dashboard. This function ensures that all the 186 | # dashboards widgets end up where they started. 187 | # 188 | # Transitions should be a `{start, end}` object suitable for passing to moveWithTransition, 189 | # or a `transitions($dashboard, widgets, originalLocations)` function which returns such an object. 190 | # 191 | showHideDashboard = (visible, stagger, $dashboard, transitions, done) -> 192 | $dashboard = $($dashboard) 193 | 194 | $ul = $dashboard.children('ul') 195 | $widgets = $ul.children('li') 196 | 197 | # Record the original location, opacity, other CSS attributes we might want to edit 198 | originalLocations = [] 199 | $widgets.each (index, widget) -> 200 | $widget = $(widget) 201 | originalLocations[index] = { 202 | left: $widget.css 'left' 203 | top: $widget.css 'top' 204 | width: $widget.css 'width' 205 | height: $widget.css 'height' 206 | opacity: $widget.css 'opacity' 207 | transform: $widget.css 'transform' 208 | "-webkit-transform": $widget.css '-webkit-transform' 209 | } 210 | 211 | widgets = $.makeArray($widgets) 212 | 213 | if isFunction transitions 214 | transitions = transitions($dashboard, widgets, originalLocations) 215 | 216 | 217 | origDone = done 218 | done = () -> 219 | sleep 0, () -> 220 | # Make sure the dashboard is in a sane state. 221 | $dashboard.toggle( visible ) 222 | 223 | sleep 0, () -> 224 | # Clear any styles we've set on the widgets. 225 | # 226 | # TODO: It would be nice to record the styles before we start, and then restore them 227 | # here, but I've found that if my laptop goes to sleep, when it wakes up, when 228 | # displaying the dashboard on Chrome, it sometimes picks up bad values for 229 | # `originalLocations`. By always forcing the style to a sane known value, we know 230 | # everything will work out in the end. 231 | # 232 | $dashboard.children('ul').children('li').attr 'style', 'position: absolute' 233 | 234 | origDone?() 235 | 236 | 237 | transitionString = "all 1s" 238 | 239 | if transitions.transitionFunction 240 | # Show/hide the dashboard with a custom function 241 | transitionFunction = transitions.transitionFunction 242 | 243 | else if !stagger 244 | transitionFunction = ($dashboard, {widgets, originalLocations}, fnDone) -> 245 | moveWithTransition widgets, { 246 | end: transitions.start 247 | }, -> sleep 0, -> 248 | if visible then $dashboard.show() 249 | moveWithTransition widgets, { 250 | start: transitions.start, 251 | end: transitions.end, 252 | transition: transitions.transition or transitionString, 253 | timeInSeconds: 1 254 | }, fnDone 255 | 256 | else 257 | transitionFunction = ($dashboard, {widgets, originalLocations}, fnDone) -> 258 | singleWidgetFn = (widget, index) -> 259 | moveWithTransition [widget], { 260 | end: transitions.start, 261 | offset: index 262 | }, -> sleep 0, -> 263 | if visible then $dashboard.show() 264 | sleep (Math.random()/2), () -> 265 | moveWithTransition [widget], { 266 | start: transitions.start, 267 | end: transitions.end, 268 | transition: transitions.transition or transitionString, 269 | timeInSeconds: 1, 270 | offset: index 271 | }, -> 272 | for widget, index in widgets 273 | singleWidgetFn(widget, index) 274 | 275 | sleep 1.5, fnDone 276 | 277 | # Show or hide the dashboard 278 | transitionFunction $dashboard, {stagger, widgets, originalLocations}, done 279 | 280 | # Select a member at random from an object. 281 | # 282 | # If 'allowedMembers' is an array of strings, then only the corresponding members will be 283 | # considered for selection. 284 | # 285 | # Returns a "{key, value}" object. 286 | pickMember = (object, allowedMembers=null) -> 287 | answer = null 288 | functionArray = [] 289 | if allowedMembers? 290 | if not isArray allowedMembers then allowedMembers = [allowedMembers] 291 | for memberName in allowedMembers 292 | if memberName of object then functionArray.push {key: memberName, value: object[memberName]} 293 | else 294 | for memberName, member of object 295 | functionArray.push {key: memberName, value: member} 296 | 297 | if functionArray.length > 0 298 | index = Math.floor(Math.random()*functionArray.length); 299 | answer = functionArray[index] 300 | 301 | return answer 302 | 303 | # Cycle the dashboard to the next dashboard. 304 | # 305 | # If a transition is already in progress, this function does nothing. 306 | Dashing.cycleDashboardsNow = do () -> 307 | transitionInProgress = false 308 | visibleIndex = 0 309 | (options = {}) -> 310 | return if transitionInProgress 311 | transitionInProgress = true 312 | 313 | {stagger, fastTransition, boardnumber, transitiontype} = options 314 | stagger = !!stagger 315 | fastTransition = !!fastTransition 316 | 317 | $dashboards = $('.gridster') 318 | 319 | # Work out which dashboard to show 320 | oldVisibleIndex = visibleIndex 321 | if boardnumber? 322 | visibleIndex = boardnumber - 1 323 | else 324 | visibleIndex++ 325 | if visibleIndex >= $dashboards.length 326 | visibleIndex = 0 327 | 328 | if oldVisibleIndex == visibleIndex 329 | # Only one dashboard. Disable fast transitions 330 | fastTransition = false 331 | 332 | doneCount = 0 333 | doneFn = () -> 334 | doneCount++ 335 | # Only set transitionInProgress to false when both the show and the hide functions 336 | # are finished. 337 | if doneCount is 2 338 | transitionInProgress = false 339 | 340 | # Hide the old dashboard 341 | hideFunction = pickMember hideFunctions 342 | 343 | showNewDashboard = () -> 344 | options.onTransition?($($dashboards[visibleIndex])) 345 | showFunction = null 346 | chainsTo = hideFunction.value.chainsTo 347 | if isString chainsTo 348 | showFunction = showFunctions[chainsTo] 349 | else if chainsTo? 350 | showFunction = {key: "chainsTo", value: chainsTo} 351 | 352 | if !showFunction 353 | showFunction = pickMember showFunctions 354 | 355 | # console.log "Showing dashboard #{visibleIndex} #{showFunction.key}" 356 | showHideDashboard true, stagger, $dashboards[visibleIndex], showFunction.value, () -> 357 | doneFn() 358 | 359 | # console.log "Hiding dashboard #{oldVisibleIndex} #{hideFunction.key}" 360 | showHideDashboard false, stagger, $dashboards[oldVisibleIndex], hideFunction.value, () -> 361 | if !fastTransition 362 | showNewDashboard() 363 | doneFn() 364 | 365 | # If fast transitions are enabled, then don't wait for the hiding animation to complete 366 | # before showing the new dashboard. 367 | if fastTransition then showNewDashboard() 368 | 369 | return null 370 | 371 | # Adapted from http://stackoverflow.com/questions/1403888/get-url-parameter-with-javascript-or-jquery 372 | getURLParameter = (name) -> 373 | encodedParameter = (RegExp(name + '=' + '(.+?)(&|$)').exec(location.search)||[null,null])[1] 374 | return if encodedParameter? then decodeURI(encodedParameter) else null 375 | 376 | # Cause dashing to cycle from one dashboard to the next. 377 | # 378 | # Dashboard cycling can be bypassed by passing a "page" parameter in the url. For example, 379 | # going to http://dashboardserver/mydashboard?page=2 will show the second dashboard in the list 380 | # and will not cycle. 381 | # 382 | # Options: 383 | # * `timeInSeconds` - The time to display each dashboard, in seconds. If 0, then dashboards will 384 | # not automatically cycle, but can be cycled manually by calling `cycleDashboardsNow()`. 385 | # * `stagger` - If this is true, each widget will be transitioned individually at slightly 386 | # randomized times. This gives a more random look. If false, then all wigets will be moved 387 | # at the same time. Note if `timeInSeconds` is 0, then this option is ignored (but can, instead, 388 | # be passed to `cycleDashboardsNow()`.) 389 | # * `fastTransition` - If true, then we will run the show and hide transitions simultaneously. 390 | # This gets your new dashboard up onto the screen faster. 391 | # * `onTransition($newDashboard)` - A function to call before a dashboard is displayed. 392 | # 393 | Dashing.cycleDashboards = (options) -> 394 | timeInSeconds = if options.timeInSeconds? then options.timeInSeconds else 20 395 | 396 | $dashboards = $('.gridster') 397 | 398 | startDashboard = if options.page? then options.page else 1 399 | startDashboard = Math.max startDashboard, 1 400 | startDashboard = Math.min startDashboard, $dashboards.length 401 | 402 | $dashboards.each (dashboardIndex, dashboard) -> 403 | # Hide all but the first dashboard. 404 | $(dashboard).toggle(dashboardIndex is (startDashboard - 1)) 405 | 406 | # Set all dashboards to position: absolute so they stack one on top of the other 407 | $(dashboard).css "position": "absolute" 408 | 409 | # If the user specified a dashboard, then don't cycle from one dashboard to the next. 410 | if !startDashboardParam? and (timeInSeconds > 0) 411 | cycleFn = () -> Dashing.cycleDashboardsNow(options) 412 | setInterval cycleFn, timeInSeconds * 1000 413 | 414 | $(document).keypress (event) -> 415 | # Cycle to next dashboard on space 416 | if event.keyCode is 32 then Dashing.cycleDashboardsNow(options) 417 | return true 418 | 419 | # Customized version of `Dashing.gridsterLayout()` which supports multiple dashboards. 420 | Dashing.cycleGridsterLayout = (positions) -> 421 | #positions = positions.replace(/^"|"$/g, '') # ?? 422 | positions = JSON.parse(positions) 423 | $dashboards = $(".gridster > ul") 424 | if isArray(positions) and ($dashboards.length == positions.length) 425 | Dashing.customGridsterLayout = true 426 | for position, index in positions 427 | $dashboard = $($dashboards[index]) 428 | widgets = $dashboard.children("[data-row^=]") 429 | for widget, index in widgets 430 | $(widget).attr('data-row', position[index].row) 431 | $(widget).attr('data-col', position[index].col) 432 | else 433 | console.log "Warning: Could not apply custom layout!" 434 | 435 | # Redefine functions for saving layout 436 | sleep 0.1, () -> 437 | Dashing.getWidgetPositions = -> 438 | dashboardPositions = [] 439 | for dashboard in $(".gridster > ul") 440 | dashboardPositions.push $(dashboard).gridster().data('gridster').serialize() 441 | return dashboardPositions 442 | 443 | Dashing.showGridsterInstructions = -> 444 | newWidgetPositions = Dashing.getWidgetPositions() 445 | 446 | if !isArray(newWidgetPositions[0]) 447 | $('#save-gridster').slideDown() 448 | $('#gridster-code').text(" 449 | Something went wrong - reload the page and try again. 450 | ") 451 | else 452 | unless JSON.stringify(newWidgetPositions) == JSON.stringify(Dashing.currentWidgetPositions) 453 | Dashing.currentWidgetPositions = newWidgetPositions 454 | $('#save-gridster').slideDown() 455 | $('#gridster-code').text(" 456 | 461 | ") -------------------------------------------------------------------------------- /assets/javascripts/dashing.gridster.coffee: -------------------------------------------------------------------------------- 1 | #= require_directory ./gridster 2 | 3 | # This file enables gridster integration (http://gridster.net/) 4 | # Delete it if you'd rather handle the layout yourself. 5 | # You'll miss out on a lot if you do, but we won't hold it against you. 6 | 7 | Dashing.gridsterLayout = (positions) -> 8 | Dashing.customGridsterLayout = true 9 | positions = positions.replace(/^"|"$/g, '') 10 | positions = $.parseJSON(positions) 11 | widgets = $("[data-row^=]") 12 | for widget, index in widgets 13 | $(widget).attr('data-row', positions[index].row) 14 | $(widget).attr('data-col', positions[index].col) 15 | 16 | Dashing.getWidgetPositions = -> 17 | $(".gridster ul:first").gridster().data('gridster').serialize() 18 | 19 | Dashing.showGridsterInstructions = -> 20 | newWidgetPositions = Dashing.getWidgetPositions() 21 | 22 | unless JSON.stringify(newWidgetPositions) == JSON.stringify(Dashing.currentWidgetPositions) 23 | Dashing.currentWidgetPositions = newWidgetPositions 24 | $('#save-gridster').slideDown() 25 | $('#gridster-code').text(" 26 | 31 | ") 32 | 33 | $ -> 34 | $('#save-gridster').leanModal() 35 | 36 | $('#save-gridster').click -> 37 | $('#save-gridster').slideUp() 38 | -------------------------------------------------------------------------------- /assets/javascripts/eventsource.min.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * eventsource.js 3 | * Available under MIT License (MIT) 4 | * https://github.com/Yaffle/EventSource/ 5 | */ 6 | !function(a){"use strict";function b(){this.data={}}function c(){this.listeners=new b}function d(a){setTimeout(function(){throw a},0)}function e(a){this.type=a,this.target=null}function f(a,b){e.call(this,a),this.data=b.data,this.lastEventId=b.lastEventId}function g(a,b){var c=Number(a)||b;return z>c?z:c>A?A:c}function h(a,b,c){try{"function"==typeof b&&b.call(a,c)}catch(e){d(e)}}function i(b,d){function i(){L=s,null!==H&&(H.abort(),H=null),0!==I&&(clearTimeout(I),I=0),0!==J&&(clearTimeout(J),J=0),E.readyState=s}function j(a){var b=L===r||L===q?H.responseText||"":"",c=null,d=!1;if(L===q){var i=0,j="",k="";if(n)try{i=Number(H.status||0),j=String(H.statusText||""),k=String(H.getResponseHeader("Content-Type")||"")}catch(l){i=0}else i=200,k=H.contentType;if(200===i&&y.test(k)){if(L=r,G=!0,F=B,E.readyState=r,c=new e("open"),E.dispatchEvent(c),h(E,E.onopen,c),L===s)return}else if(0!==i){var m="";m=200!==i?"EventSource's response has a status "+i+" "+j.replace(/\s+/g," ")+" that is not 200. Aborting the connection.":"EventSource's response has a Content-Type specifying an unsupported type: "+k.replace(/\s+/g," ")+". Aborting the connection.",setTimeout(function(){throw new Error(m)}),d=!0}}if(L===r){b.length>K&&(G=!0);for(var o=K-1,z=b.length,J="\n";++o1048576||0===I&&!G)?0===I&&(G=!1,I=setTimeout(P,C)):(L=p,H.abort(),0!==I&&(clearTimeout(I),I=0),F>16*B&&(F=16*B),F>A&&(F=A),I=setTimeout(P,F),F=2*F+1,E.readyState=q,c=new e("error"),E.dispatchEvent(c),h(E,E.onerror,c))}function k(){j(!1)}function l(){j(!0)}b=String(b);var z=Boolean(m&&d&&d.withCredentials),B=g(d?d.retry:0/0,1e3),C=g(d?d.heartbeatTimeout:0/0,45e3),D=d&&d.lastEventId&&String(d.lastEventId)||"",E=this,F=B,G=!1,H=new o,I=0,J=0,K=0,L=p,M=[],N="",O="",P=null,Q=u,R="",S="";d=null,n&&(J=setTimeout(function T(){3===H.readyState&&k(),J=setTimeout(T,500)},0)),P=function(){if(I=0,L!==p)return void j(!1);if(n&&(void 0!==H.sendAsBinary||void 0===H.onloadend)&&a.document&&a.document.readyState&&"complete"!==a.document.readyState)return void(I=setTimeout(P,4));H.onload=H.onerror=l,n&&(H.onabort=l,H.onreadystatechange=k),H.onprogress=k,G=!1,I=setTimeout(P,C),K=0,L=q,M.length=0,O="",N=D,S="",R="",Q=u;var c=b.slice(0,5);c="data:"!==c&&"blob:"!==c?b+((-1===b.indexOf("?",0)?"?":"&")+"lastEventId="+encodeURIComponent(D)+"&r="+String(Math.random()+1).slice(2)):b,H.open("GET",c,!0),n&&(H.withCredentials=z,H.responseType="text",H.setRequestHeader("Accept","text/event-stream")),H.send(null)},c.call(this),this.close=i,this.url=b,this.readyState=q,this.withCredentials=z,this.onopen=null,this.onmessage=null,this.onerror=null,P()}function j(){this.CONNECTING=q,this.OPEN=r,this.CLOSED=s}b.prototype={get:function(a){return this.data[a+"~"]},set:function(a,b){this.data[a+"~"]=b},"delete":function(a){delete this.data[a+"~"]}},c.prototype={dispatchEvent:function(a){a.target=this;var b=String(a.type),c=this.listeners,e=c.get(b);if(e)for(var f=e.length,g=-1,h=null;++g=0;)if(d[e]===b)return;d.push(b)},removeEventListener:function(a,b){a=String(a);var c=this.listeners,d=c.get(a);if(d){for(var e=d.length,f=[],g=-1;++g");$("body").append(overlay);options=$.extend(defaults,options);return this.each(function(){var o=options;$(this).click(function(e){var modal_id=$(this).attr("href");$("#lean_overlay").click(function(){close_modal(modal_id)});$(o.closeButton).click(function(){close_modal(modal_id)});var modal_height=$(modal_id).outerHeight();var modal_width=$(modal_id).outerWidth(); 5 | $("#lean_overlay").css({"display":"block",opacity:0});$("#lean_overlay").fadeTo(200,o.overlay);$(modal_id).css({"display":"block","position":"fixed","opacity":0,"z-index":11000,"left":50+"%","margin-left":-(modal_width/2)+"px","top":o.top+"px"});$(modal_id).fadeTo(200,1);e.preventDefault()})});function close_modal(modal_id){$("#lean_overlay").fadeOut(200);$(modal_id).css({"display":"none"})}}})})(jQuery); 6 | -------------------------------------------------------------------------------- /assets/stylesheets/_variables.scss: -------------------------------------------------------------------------------- 1 | // Core colors. Reference these wherever possible rather than making custom 2 | // versions of red, green, etc. This will make it easier to quickly change the 3 | // overall pallette of the dashboard just by overriding these variables. 4 | 5 | // This file contains the application "defaults". 6 | // IF YOU WANT TO CUSTOMIZE COLORS FOR YOUR OWN INSTANCE, DO NOT EDIT THIS FILE. 7 | 8 | // ---------------------------------------------------------------------------- 9 | // Custom styles instructions 10 | // 1. Create a file named "_variables_custom.scss" (in this folder) 11 | // 2. Uncomment the @import line at the bottom of this file 12 | // ---------------------------------------------------------------------------- 13 | 14 | $white: #fff; 15 | $red: #ff0055; 16 | $green: #aaff00; 17 | $blue: #00aaff; 18 | $purple: #aa00ff; 19 | $yellow: #ffff00; 20 | $orange: #ffaa00; 21 | 22 | $gray-dark: #444; 23 | $gray-medium: #666; 24 | $gray-light: #888; 25 | 26 | //Page and widget defaults 27 | $background-color: #222; 28 | $text-color: #fff; 29 | 30 | //These are used for icons and indicators 31 | $color-inactive: $gray-light; 32 | $color-active: $green; 33 | 34 | //Warnings and alerts 35 | $background-warning-color-1: #e82711; 36 | $background-warning-color-2: #9b2d23; 37 | $text-warning-color: #fff; 38 | 39 | $background-danger-color-1: #eeae32; 40 | $background-danger-color-2: #ff9618; 41 | $text-danger-color: #fff; 42 | 43 | // Uncomment to include your custom variables/overrides 44 | // @import "variables_custom" 45 | -------------------------------------------------------------------------------- /assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | //=require_directory . 3 | //=require_tree ../../widgets 4 | */ 5 | // ---------------------------------------------------------------------------- 6 | // Sass declarations 7 | // ---------------------------------------------------------------------------- 8 | 9 | //Default colors are all in _variables.scss 10 | @import "variables"; 11 | // ---------------------------------------------------------------------------- 12 | // Custom colors - if you want to override the default colors, 13 | // go to _variables.scss and follow the instructions there 14 | // ---------------------------------------------------------------------------- 15 | 16 | @-webkit-keyframes status-warning-background { 17 | 0% { background-color: $background-warning-color-1; } 18 | 50% { background-color: $background-warning-color-2; } 19 | 100% { background-color: $background-warning-color-1; } 20 | } 21 | @-webkit-keyframes status-danger-background { 22 | 0% { background-color: $background-danger-color-1; } 23 | 50% { background-color: $background-danger-color-2; } 24 | 100% { background-color: $background-danger-color-1; } 25 | } 26 | @mixin animation($animation-name, $duration, $function, $animation-iteration-count:""){ 27 | -webkit-animation: $animation-name $duration $function #{$animation-iteration-count}; 28 | -moz-animation: $animation-name $duration $function #{$animation-iteration-count}; 29 | -ms-animation: $animation-name $duration $function #{$animation-iteration-count}; 30 | } 31 | 32 | // ---------------------------------------------------------------------------- 33 | // Base styles 34 | // ---------------------------------------------------------------------------- 35 | html { 36 | font-size: 100%; 37 | -webkit-text-size-adjust: 100%; 38 | -ms-text-size-adjust: 100%; 39 | } 40 | 41 | body { 42 | margin: 0; 43 | background-color: $background-color; 44 | font-size: 15px; 45 | color: $text-color; 46 | font-family: 'Helvetica Neue', 'Helvetica', 'Open Sans', 'Arial' 47 | } 48 | 49 | b, strong { 50 | font-weight: bold; 51 | } 52 | 53 | a { 54 | text-decoration: none; 55 | color: inherit; 56 | } 57 | 58 | img { 59 | border: 0; 60 | -ms-interpolation-mode: bicubic; 61 | vertical-align: middle; 62 | } 63 | 64 | img, object { 65 | max-width: 100%; 66 | } 67 | 68 | iframe { 69 | max-width: 100%; 70 | } 71 | 72 | table { 73 | border-collapse: collapse; 74 | border-spacing: 0; 75 | width: 100%; 76 | } 77 | 78 | td { 79 | vertical-align: middle; 80 | } 81 | 82 | ul, ol { 83 | padding: 0; 84 | margin: 0; 85 | } 86 | 87 | h1, h2, h3, h4, h5, p { 88 | padding: 0; 89 | margin: 0; 90 | } 91 | h1 { 92 | margin-bottom: 6px; 93 | text-align: center; 94 | font-size: 100%; 95 | font-weight: 200; 96 | } 97 | h2 { 98 | text-transform: uppercase; 99 | font-size: 300%; 100 | font-weight: 400; 101 | color: $text-color; 102 | } 103 | h3 { 104 | font-size: 125%; 105 | font-weight: 300; 106 | color: $text-color; 107 | } 108 | 109 | // ---------------------------------------------------------------------------- 110 | // Base widget styles 111 | // ---------------------------------------------------------------------------- 112 | .gridster { 113 | margin: 0px auto; 114 | } 115 | 116 | .icon-background { 117 | pointer-events: none; 118 | width: 100%!important; 119 | height: 100%; 120 | position: absolute; 121 | left: 0; 122 | top: 0; 123 | opacity: 0.1; 124 | font-size: 1375%; 125 | text-align: center; 126 | margin-top: 82px; 127 | } 128 | 129 | .list-nostyle { 130 | list-style: none; 131 | } 132 | 133 | .gridster ul { 134 | list-style: none; 135 | } 136 | 137 | .gs_w { 138 | width: 100%; 139 | display: table; 140 | cursor: pointer; 141 | } 142 | 143 | .widget { 144 | padding: 0px 0px; 145 | text-align: center; 146 | width: 100%; 147 | display: table-cell; 148 | vertical-align: middle; 149 | background-color: $gray-dark; 150 | 151 | .title { 152 | color: #fff; 153 | } 154 | 155 | .icon-inactive { 156 | color: $color-inactive; 157 | } 158 | 159 | .icon-active { 160 | color: $color-active; 161 | } 162 | } 163 | 164 | .widget.status-warning { 165 | background-color: $background-warning-color-1; 166 | @include animation(status-warning-background, 2s, ease, infinite); 167 | 168 | .icon-warning-sign { 169 | display: inline-block; 170 | } 171 | 172 | .title, .more-info { 173 | color: $text-warning-color; 174 | } 175 | } 176 | 177 | .widget.status-danger { 178 | color: $text-danger-color; 179 | background-color: $background-danger-color-1; 180 | @include animation(status-danger-background, 2s, ease, infinite); 181 | 182 | .icon-warning-sign { 183 | display: inline-block; 184 | } 185 | 186 | .title, .more-info { 187 | color: $text-danger-color; 188 | } 189 | } 190 | 191 | .more-info { 192 | font-size: 75%; 193 | position: absolute; 194 | bottom: 16px; 195 | left: 0; 196 | right: 0; 197 | } 198 | 199 | .updated-at { 200 | font-size: 75%; 201 | position: absolute; 202 | bottom: 12px; 203 | left: 0; 204 | right: 0; 205 | } 206 | 207 | #save-gridster { 208 | display: none; 209 | position: fixed; 210 | top: 0; 211 | margin: 0px auto; 212 | left: 50%; 213 | z-index: 1000; 214 | background: black; 215 | width: 190px; 216 | text-align: center; 217 | border: 1px solid white; 218 | border-top: 0px; 219 | margin-left: -95px; 220 | padding: 15px; 221 | } 222 | 223 | #save-gridster:hover { 224 | padding-top: 25px; 225 | } 226 | 227 | #saving-instructions { 228 | display: none; 229 | padding: 10px; 230 | width: 500px; 231 | height: 122px; 232 | z-index: 1000; 233 | background: white; 234 | top: 100px; 235 | color: black; 236 | font-size: 75px; 237 | padding-bottom: 4px; 238 | 239 | textarea { 240 | white-space: nowrap; 241 | width: 494px; 242 | height: 80px; 243 | } 244 | } 245 | 246 | #lean_overlay { 247 | position: fixed; 248 | z-index:100; 249 | top: 0px; 250 | left: 0px; 251 | height:100%; 252 | width:100%; 253 | background: #000; 254 | display: none; 255 | } 256 | 257 | #container { 258 | padding-top: 5px; 259 | } 260 | 261 | // ---------------------------------------------------------------------------- 262 | // Clearfix 263 | // ---------------------------------------------------------------------------- 264 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 265 | .clearfix:after { clear: both; } 266 | .clearfix { zoom: 1; } 267 | 268 | 269 | // ---------------------------------------------------------------------------- 270 | // Custom styles instructions 271 | // 1. Create a file named "_application_custom.scss" (in this folder) 272 | // 2. Uncomment the below @import line 273 | // ---------------------------------------------------------------------------- 274 | // @import "application_custom"; 275 | -------------------------------------------------------------------------------- /assets/stylesheets/climacons-font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Climacons-Font'; 3 | src:url('climacons-webfont.eot'); 4 | src:url('climacons-webfont.eot?#iefix') format('embedded-opentype'), 5 | url('climacons-webfont.svg#Climacons-Font') format('svg'), 6 | url('climacons-webfont.woff') format('woff'), 7 | url('climacons-webfont.ttf') format('truetype'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } -------------------------------------------------------------------------------- /assets/stylesheets/jquery.gridster.css: -------------------------------------------------------------------------------- 1 | /*! gridster.js - v0.1.0 - 2012-08-14 2 | * http://gridster.net/ 3 | * Copyright (c) 2012 ducksboard; Licensed MIT */ 4 | 5 | .gridster { 6 | position:relative; 7 | } 8 | 9 | .gridster > * { 10 | margin: 0 auto; 11 | -webkit-transition: height .4s; 12 | -moz-transition: height .4s; 13 | -o-transition: height .4s; 14 | -ms-transition: height .4s; 15 | transition: height .4s; 16 | } 17 | 18 | .gridster .gs_w{ 19 | z-index: 2; 20 | position: absolute; 21 | } 22 | 23 | .ready .gs_w:not(.preview-holder) { 24 | -webkit-transition: opacity .3s, left .3s, top .3s; 25 | -moz-transition: opacity .3s, left .3s, top .3s; 26 | -o-transition: opacity .3s, left .3s, top .3s; 27 | transition: opacity .3s, left .3s, top .3s; 28 | } 29 | 30 | .gridster .preview-holder { 31 | z-index: 1; 32 | position: absolute; 33 | background-color: #fff; 34 | border-color: #fff; 35 | opacity: 0.3; 36 | } 37 | 38 | .gridster .player-revert { 39 | z-index: 10!important; 40 | -webkit-transition: left .3s, top .3s!important; 41 | -moz-transition: left .3s, top .3s!important; 42 | -o-transition: left .3s, top .3s!important; 43 | transition: left .3s, top .3s!important; 44 | } 45 | 46 | .gridster .dragging { 47 | z-index: 10!important; 48 | -webkit-transition: all 0s !important; 49 | -moz-transition: all 0s !important; 50 | -o-transition: all 0s !important; 51 | transition: all 0s !important; 52 | } 53 | 54 | /* Uncomment this if you set helper : "clone" in draggable options */ 55 | /*.gridster .player { 56 | opacity:0; 57 | }*/ -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'dashing' 2 | 3 | map Sinatra::Application.assets_prefix do 4 | run Sinatra::Application.sprockets 5 | end 6 | 7 | run Sinatra::Application -------------------------------------------------------------------------------- /dashboards/example.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %>HADashboard<% end %> 2 | 3 |
4 |
    5 |
  • 6 |
    7 |
  • 8 | 9 |
  • 10 |
    11 |
  • 12 | 13 |
  • 14 |
    15 |
  • 16 | 17 |
  • 18 |
    19 |
  • 20 | 21 |
  • 22 |
    23 |
  • 24 | 25 |
  • 26 |
    27 |
  • 28 | 29 |
  • 30 |
    31 |
  • 32 | 33 |
  • 34 |
    35 |
  • 36 | 37 |
  • 38 |
    39 |
  • 40 | 41 |
  • 42 |
    43 |
  • 44 | 45 |
  • 46 |
    47 |
  • 48 | 49 |
  • 50 |
    51 |
  • 52 | 53 |
  • 54 |
    55 |
  • 56 | 57 |
  • 58 |
    59 |
  • 60 | 61 |
  • 62 |
    63 |
  • 64 | 65 | 66 |
  • 67 |
    68 |
  • 69 | 70 |
  • 71 |
    72 |
  • 73 | 74 | 75 |
  • 76 |
    77 |
  • 78 | 79 |
  • 80 |
    81 |
  • 82 | 83 |
  • 84 |
    85 |
  • 86 | 87 |
  • 88 |
    89 |
  • 90 | 91 |
  • 92 |
    93 |
  • 94 | 95 |
  • 96 |
    97 |
  • 98 | 99 |
  • 100 |
    101 |
  • 102 | 103 |
  • 104 |
    105 |
  • 106 | 107 |
  • 108 |
    109 |
  • 110 | 111 |
112 |
113 | 114 |
115 |
    116 |
  • 117 |
    118 |
  • 119 |
  • 120 |
    121 |
  • 122 |
  • 123 |
    124 |
  • 125 | 126 |
  • 127 |
    128 |
  • 129 | 130 |
  • 131 |
    132 |
  • 133 | 134 | 135 |
  • 136 |
    137 |
  • 138 | 139 | 140 |
  • 141 |
    142 |
  • 143 | 144 |
  • 145 |
    146 |
  • 147 | 148 |
  • 149 |
    150 |
  • 151 | 152 |
153 |
154 | 155 |
156 |
    157 |
  • 158 |
    159 |
  • 160 |
  • 161 |
    162 |
  • 163 |
  • 164 |
    165 |
  • 166 |
  • 167 |
    168 |
  • 169 |
  • 170 |
    171 |
  • 172 |
  • 173 |
    174 |
  • 175 |
  • 176 |
    177 |
  • 178 |
  • 179 |
    180 |
  • 181 |
  • 182 |
    183 |
  • 184 |
  • 185 |
    186 |
  • 187 |
  • 188 |
    189 |
  • 190 |
  • 191 |
    192 |
  • 193 |
  • 194 |
    195 |
  • 196 |
  • 197 |
    198 |
  • 199 |
  • 200 |
    201 |
  • 202 |
  • 203 |
    204 |
  • 205 | 206 |
  • 207 |
    208 |
  • 209 | 210 |
  • 211 |
    212 |
  • 213 | 214 |
  • 215 |
    216 |
  • 217 | 218 |
  • 219 |
    220 |
  • 221 | 222 |
223 |
224 | -------------------------------------------------------------------------------- /dashboards/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= yield_content(:title) %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | <%= yield %> 23 |
24 | 25 | <% if development? %> 26 |
27 |

Paste the following at the top of <%= params[:dashboard] %>.erb

28 | 29 |
30 | Save this layout 31 | <% end %> 32 | 33 | 34 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /dashing_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /home/pi/homeassistant 4 | 5 | /usr/local/bin/dashing start 6 | -------------------------------------------------------------------------------- /hadashboard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'thor' 4 | require 'securerandom' 5 | 6 | class HADashboardCLI < Thor 7 | include Thor::Actions 8 | 9 | desc "setup", "Sets up the Heroku app and its environment." 10 | def setup 11 | say "Authorizing with Heroku", :green 12 | say "NOTE: Make sure to upload a public key if prompted!", :green 13 | system "heroku login" 14 | username = system "heroku auth:whoami" 15 | say "" 16 | 17 | say "Creating New App", :green 18 | system "heroku create" 19 | output = system "heroku app:info -s | grep ^name=" 20 | app_info = shell_to_hash output 21 | domain_name = app_info["domain_name"] 22 | app_name = app_info["name"] 23 | if not app_name 24 | say "*** Could not retrieve app name.", :red 25 | return 26 | end 27 | say "" 28 | 29 | say "Adding PostgreSQL Add-on", :green 30 | system "heroku addons:add heroku-postgresql:hobby-dev" 31 | say "" 32 | 33 | say "Creating API Client", :green 34 | system "heroku plugins:install https://github.com/heroku/heroku-oauth" 35 | output = system "heroku clients:create -s \"hadashboard\""\ 36 | "https://#{app_name}.herokuapp.com/auth/heroku/callback" 37 | client_auth = shell_to_hash output 38 | say "" 39 | 40 | say "Requesting SmartApp Credentials", :green 41 | st_client_id = nil 42 | loop do 43 | st_client_id = ask "SmartApp OAuth Client ID" 44 | break if validate_uuid st_client_id 45 | say "*** Value entered is not a UUID. Typo?", :red 46 | end 47 | 48 | st_client_secret = nil 49 | loop do 50 | st_client_secret = ask "SmartApp OAuth Client Secret" 51 | break if validate_uuid st_client_secret 52 | say "*** Value entered is not a UUID. Typo?", :red 53 | end 54 | say "" 55 | 56 | say "Configuring Heroku Variables", :green 57 | cvars = "" 58 | cvars << "DASHING_AUTH_TOKEN=" << SecureRandom.uuid << " " 59 | cvars << "DASHING_URI=http://" << domain_name << " " 60 | cvars << "HEROKU_OAUTH_EMAIL=" << username << " " 61 | cvars << "HEROKU_OAUTH_ID=" << client_auth["HEROKU_OAUTH_ID"] << " " 62 | cvars << "HEROKU_OAUTH_SECRET=" << client_auth["HEROKU_OAUTH_SECRET"] << " " 63 | cvars << "SESSION_SECRET=" << SecureRandom.uuid << " " 64 | cvars << "ST_CLIENT_ID=" << st_client_id << " " 65 | cvars << "ST_CLIENT_SECRET=" << st_client_secret << " " 66 | system "heroku config:set #{cvars}" 67 | say "" 68 | 69 | say "Waiting for Database", :green 70 | say "... this may take up to 5 minutes!", :green 71 | system "heroku pg:wait" 72 | say "" 73 | 74 | say "Deploying App", :green 75 | system "git push heroku master" 76 | say "" 77 | 78 | say "Opening App", :green 79 | say "NOTE: Don't forget to authorize with the SmartApp at: "\ 80 | "#{domain_name}/smartthings/authorize", :green 81 | system "heroku open" 82 | say "" 83 | end 84 | 85 | private 86 | 87 | def shell_to_hash(shell_output) 88 | Hash[shell_output.each_line.map { |l| l.chomp.split "=", 2 }] 89 | end 90 | 91 | def validate_uuid(value) 92 | (value =~ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/) == 0 93 | end 94 | 95 | end 96 | 97 | HADashboardCLI.start(ARGV) 98 | -------------------------------------------------------------------------------- /hapush/.gitignore: -------------------------------------------------------------------------------- 1 | hapush.cfg 2 | hapush.log 3 | -------------------------------------------------------------------------------- /hapush/hapush.cfg.example: -------------------------------------------------------------------------------- 1 | ha_url = "http://192.168.1.10:8123" 2 | ha_key = api_key 3 | dash_host = "192.168.1.10:3030" 4 | dash_dir = "/srv/hass/src/hadashboard/dashboards" 5 | logfile = "/etc/hapush/hapush.log" 6 | 7 | -------------------------------------------------------------------------------- /hapush/hapush.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import json 4 | import traceback 5 | import re 6 | import requests 7 | from configobj import ConfigObj 8 | import datetime 9 | import argparse 10 | import time 11 | from daemonize import Daemonize 12 | import logging 13 | import glob 14 | import os.path 15 | import math 16 | import pprint 17 | from sseclient import SSEClient 18 | from logging.handlers import RotatingFileHandler 19 | 20 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 21 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | ha_url = "" 26 | ha_key = "" 27 | dash_host = "" 28 | dash_dir = "" 29 | widgets = {} 30 | monitored_files = {} 31 | 32 | def roundup(x): 33 | return int(math.ceil(x / 10.0)) * 10 34 | 35 | def call_ha(widget_id, values): 36 | global logger 37 | url = "http://" + dash_host + "/widgets/" + widget_id 38 | logger.debug(url) 39 | logger.debug(str(values)) 40 | try: 41 | response = requests.post(url, verify = False, json = values) 42 | except requests.exceptions.RequestException as e: 43 | logger.warn("Unexpected error calling Dashing: %s", e) 44 | 45 | def process_message(msg): 46 | global logger 47 | global widgets 48 | 49 | if msg.data == "ping": 50 | return 51 | 52 | # Check to see if dashboards have changed 53 | readDashboards() 54 | 55 | try: 56 | data = json.loads(msg.data) 57 | logger.debug("Event type:{}:".format(data['event_type'])) 58 | if data['event_type'] == "state_changed": 59 | try: 60 | entity_id = data['data']['new_state']['entity_id'] 61 | logger.debug("Entity ID:{}:".format(entity_id)) 62 | parts = entity_id.split(".") 63 | type = parts[0] 64 | widget_id = parts[1] 65 | 66 | # Check to see if we have the widget registered and also where to send the notification 67 | 68 | if type in widgets and widget_id in widgets[type]: 69 | for widget in widgets[type][widget_id]: 70 | send_type = widgets[type][widget_id][widget] 71 | state = data['data']['new_state'] 72 | # Send it 73 | dashboard_update(widget, send_type, state) 74 | except: TypeError 75 | 76 | except ValueError as e: 77 | logger.warn("Malformed JSON: %s", e) 78 | logger.warn("%s", msg) 79 | except: 80 | logger.warn("Unexpected error:") 81 | logger.warn('-'*60) 82 | logger.warn(traceback.format_exc()) 83 | logger.warn('-'*60) 84 | 85 | def dashboard_update(widget_id, type, state): 86 | 87 | try: 88 | if type == "switch": 89 | values = {"state": state['state']} 90 | logger.info("switch." + widget_id + " -> " + state['state']) 91 | call_ha(widget_id, values) 92 | elif type == "group": 93 | values = {"state": state['state']} 94 | logger.info("group." + widget_id + " -> " + state['state']) 95 | call_ha(widget_id, values) 96 | elif type == "device_tracker": 97 | if state['state'] == "not_home": 98 | nstate = "away" 99 | else: 100 | nstate = state['state'] 101 | values = {"state": nstate.upper()} 102 | logger.info("devicetracker." + widget_id + " -> " + state['state']) 103 | call_ha(widget_id, values) 104 | elif type == "input_select": 105 | values = {"value": state['state']} 106 | logger.info("input_select." + widget_id + " -> " + state['state']) 107 | call_ha(widget_id, values) 108 | elif type == "input_boolean": 109 | values = {"state": state['state']} 110 | logger.info("input_boolean." + widget_id + " -> " + state['state']) 111 | call_ha(widget_id, values) 112 | elif type == "binary_sensor": 113 | values = {"state": state['state']} 114 | logger.info("binary_sensor." + widget_id + " -> " + state['state']) 115 | call_ha(widget_id, values) 116 | elif type == "sensor": 117 | values = {"value": state['state']} 118 | logger.info("sensor." + widget_id + " -> " + state['state']) 119 | call_ha(widget_id, values) 120 | elif type == "light": 121 | astate = state['state'] 122 | try: 123 | brightness = roundup(int(state['attributes']['brightness'])/2.55) 124 | except KeyError: 125 | brightness = 30 126 | astate = 'off' 127 | values = {"state": astate, "level": brightness} 128 | logger.info("switch." + widget_id + " -> state = " + astate + ", brightness = " + str(brightness)) 129 | call_ha(widget_id, values) 130 | elif type == "garage_door": 131 | values = {"state": state['state']} 132 | logger.info("garage_door." + widget_id + " -> " + state['state']) 133 | call_ha(widget_id, values) 134 | elif type == "cover": 135 | values = {"state": state['state']} 136 | logger.info("cover." + widget_id + " -> " + state['state']) 137 | call_ha(widget_id, values) 138 | elif type == "lock": 139 | values = {"state": state['state']} 140 | logger.info("lock." + widget_id + " -> " + state['state']) 141 | call_ha(widget_id, values) 142 | elif type == "alarm_control_panel": 143 | values = {"value": state['state']} 144 | logger.info("alarm_control_panel." + widget_id + " -> " + state['state']) 145 | call_ha(widget_id, values) 146 | elif type == "media_player": 147 | logger.info("media_player." + widget_id + " -> " + str(state)) 148 | call_ha(widget_id, state) 149 | elif type == "script": 150 | values = {"mode": state['state']} 151 | logger.info("script." + widget_id + " -> " + state['state']) 152 | call_ha(widget_id, values) 153 | except: 154 | logger.warn("Unexpected error:") 155 | logger.warn('-'*60) 156 | logger.warn(traceback.format_exc()) 157 | logger.warn('-'*60) 158 | 159 | def translate_view(view): 160 | views = { 161 | "Hadevicetracker": "device_tracker", 162 | "Hagarage": "garage_door", 163 | "Hacover": "cover", 164 | "Halock": "lock", 165 | "Haalarmstatus": "alarm_control_panel", 166 | "Hainputboolean": "input_boolean", 167 | "Halux": "sensor", 168 | "Hascene": "scene", 169 | "Haswitch": "switch", 170 | "Hagroup": "group", 171 | "Hadimmer": "light", 172 | "Hahumidity": "sensor", 173 | "Hainputselect": "input_select", 174 | "Hamotion": "binary_sensor", 175 | "Habinary": "binary_sensor", 176 | "Hamode": "script", 177 | "Hatemp": "sensor", 178 | "Hasensor": "sensor", 179 | "Hameter": "sensor", 180 | "Hamediaplayer": "media_player" 181 | } 182 | if view in views: 183 | return views[view] 184 | else: 185 | return "none" 186 | 187 | 188 | def readDash(file): 189 | global widgets 190 | 191 | logger.info("Reading dashboard: %s", file) 192 | 193 | div = re.compile('') 194 | id = re.compile('data-id\s*?=\s*?"(.+?)"') 195 | input = re.compile('data-input\s*?=\s*?"(.+?)"') 196 | view = re.compile('data-view\s*?=\s*?"(.+?)"') 197 | 198 | with open(file) as f: 199 | for line in f: 200 | 201 | data_id = "" 202 | data_input = "" 203 | data_view = "" 204 | 205 | # Find a
206 | m1 = div.search(line) 207 | if m1: 208 | # Check for data-id 209 | m2 = id.search(m1.group()) 210 | if m2: 211 | # grab data-id 212 | data_id = m2.group(1) 213 | # grab data-view 214 | m3 = view.search(m1.group()) 215 | if m3: 216 | data_view = (translate_view(m3.group(1))) 217 | # grab data-input 218 | m4 = input.search(m1.group()) 219 | if m4: 220 | data_input = m4.group(1) 221 | 222 | if data_id: 223 | if not data_view in widgets: 224 | widgets[data_view] = {} 225 | if (data_input): 226 | # If we have a data-input this is a special case of a script that shows status based on an input_select 227 | # We need to register it as interested in events from that input select 228 | if not "input_select" in widgets: 229 | widgets["input_select"] = {} 230 | if not data_input in widgets["input_select"]: 231 | widgets["input_select"][data_input] = {} 232 | widgets["input_select"][data_input][data_id] = data_view 233 | else: 234 | if not data_id in widgets[data_view]: 235 | widgets[data_view][data_id] = {} 236 | widgets[data_view][data_id][data_id] = data_view 237 | 238 | def readDashboards(): 239 | global monitored_files 240 | global dash_dir 241 | 242 | found_files = glob.glob(os.path.join(dash_dir, '*.erb')) 243 | for file in found_files: 244 | if file == "{0}/layout.erb".format(dash_dir): 245 | continue 246 | modified = os.path.getmtime(file) 247 | 248 | if file in monitored_files: 249 | if monitored_files[file] < modified: 250 | readDash(file) 251 | monitored_files[file] = modified 252 | else: 253 | readDash(file) 254 | monitored_files[file] = modified 255 | 256 | def run(): 257 | 258 | global ha_url 259 | global ha_key 260 | global dash_host 261 | global logger 262 | 263 | while True: 264 | try: 265 | headers = {'Content-Type' : 'application/json'} 266 | if ha_key != "": 267 | headers['x-ha-access'] = ha_key 268 | messages = SSEClient(ha_url + "/api/stream", verify = False, headers = headers, retry = 3000) 269 | for msg in messages: 270 | process_message(msg) 271 | except requests.exceptions.ConnectionError: 272 | logger.warning("Unable to connect to Home Assistant, retrying in 5 seconds") 273 | except: 274 | logger.fatal("Unexpected error:") 275 | logger.fatal('-'*60) 276 | logger.fatal(traceback.format_exc()) 277 | logger.fatal('-'*60) 278 | time.sleep(5) 279 | 280 | def main(): 281 | 282 | global ha_url 283 | global ha_key 284 | global dash_host 285 | global dash_dir 286 | global logger 287 | 288 | # Get command line args 289 | 290 | parser = argparse.ArgumentParser() 291 | 292 | parser.add_argument("config", help="full path to config file", type=str) 293 | parser.add_argument("-d", "--daemon", help="run as a background process", action="store_true") 294 | parser.add_argument("-p", "--pidfile", help="full path to PID File", default = "/tmp/hapush.pid") 295 | parser.add_argument("-D", "--debug", help="debug level", default = "INFO", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]) 296 | args = parser.parse_args() 297 | config_file = args.config 298 | 299 | isdaemon = args.daemon 300 | 301 | # Read Config File 302 | 303 | config = ConfigObj(config_file, file_error=True) 304 | 305 | ha_url = config['ha_url'] 306 | if 'ha_key' in config: 307 | ha_key = config['ha_key'] 308 | dash_host = config['dash_host'] 309 | dash_dir = config['dash_dir'] 310 | logfile = config['logfile'] 311 | 312 | # Setup Logging 313 | 314 | logger = logging.getLogger(__name__) 315 | numeric_level = getattr(logging, args.debug, None) 316 | logger.setLevel(numeric_level) 317 | logger.propagate = False 318 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') 319 | 320 | # Send to file if we are daemonizing, else send to console 321 | 322 | if isdaemon: 323 | fh = RotatingFileHandler(logfile, maxBytes=1000000, backupCount=3) 324 | fh.setLevel(numeric_level) 325 | fh.setFormatter(formatter) 326 | logger.addHandler(fh) 327 | else: 328 | ch = logging.StreamHandler() 329 | ch.setLevel(numeric_level) 330 | ch.setFormatter(formatter) 331 | logger.addHandler(ch) 332 | 333 | # Read dashboards 334 | 335 | readDashboards() 336 | 337 | # Start main loop 338 | 339 | if isdaemon: 340 | keep_fds = [fh.stream.fileno()] 341 | pid = args.pidfile 342 | daemon = Daemonize(app="hapush", pid=pid, action=run, keep_fds=keep_fds) 343 | daemon.start() 344 | while True: 345 | time.sleep(1) 346 | else: 347 | run() 348 | 349 | 350 | 351 | if __name__ == "__main__": 352 | main() 353 | -------------------------------------------------------------------------------- /images/alarm_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/images/alarm_panel.png -------------------------------------------------------------------------------- /images/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/home-assistant/hadashboard/74bc478cce3bbb17a531c638a6c4c52e05a1dee8/images/dash.png -------------------------------------------------------------------------------- /init/.gitignore: -------------------------------------------------------------------------------- 1 | mqttbridge.service 2 | -------------------------------------------------------------------------------- /init/dashing: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Dashing service 3 | # Add this file to /etc/init.d/ 4 | # $ sudo cp dashboard /etc/init.d/ 5 | # Update variables DASHING_DIR, GEM_HOME, & PATH to suit your installation 6 | # $ sudo nano /etc/init.d/dashboard 7 | # Make executable 8 | # $ sudo chmod 755 /etc/init.d/dashboard 9 | # Update rc.d 10 | # $ sudo update-rc.d dashboard defaults 11 | # Dashboard will start at boot. Check out the boot log for trouble shooting "/var/log/boot.log" 12 | # USAGE: start|stop|status|logs 13 | 14 | ### BEGIN INIT INFO 15 | # Provides: dashboard 16 | # Required-Start: $remote_fs $syslog 17 | # Required-Stop: $remote_fs $syslog 18 | # Default-Start: 2 3 4 5 19 | # Default-Stop: 0 1 6 20 | # Short-Description: Start daemon at boot time 21 | # Description: Enable service provided by daemon. 22 | ### END INIT INFO 23 | 24 | set -e 25 | 26 | # Must be a valid filename 27 | NAME=dashing 28 | DASHING_DIR=/srv/hass/src/hadashboard 29 | DAEMON=/usr/local/bin/dashing 30 | PIDFILE="$DASHING_DIR/$NAME.pid" 31 | DAEMON_OPTS="start -d -P $PIDFILE" 32 | GEM_HOME=/usr/local/lib/ruby/gems/1.9.1 33 | 34 | export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games 35 | 36 | case "$1" in 37 | start) 38 | echo -n "Starting daemon: "$NAME 39 | start-stop-daemon --start --quiet --chdir $DASHING_DIR --exec $DAEMON -- $DAEMON_OPTS 40 | echo "." 41 | ;; 42 | stop) 43 | echo -n "Stopping daemon: "$NAME 44 | start-stop-daemon --stop --quiet --signal 9 --oknodo --pidfile $PIDFILE 45 | echo "." 46 | ;; 47 | restart) 48 | echo -n "Restarting daemon: "$NAME 49 | start-stop-daemon --stop --quiet --signal 9 --oknodo --retry 30 --pidfile $PIDFILE 50 | start-stop-daemon --start --quiet --chdir $DASHING_DIR --exec $DAEMON -- $DAEMON_OPTS 51 | echo "." 52 | ;; 53 | logs) 54 | echo "See the logs of the Dashing." 55 | tail -f $DASHING_DIR'log/thin.log' 56 | ;; 57 | 58 | *) 59 | echo "Usage: "$1" {start|stop|restart}" 60 | exit 1 61 | esac 62 | 63 | exit 0 64 | -------------------------------------------------------------------------------- /init/dashing.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Home Assistant Dashboard 3 | 4 | [Service] 5 | ExecStart=/etc/init.d/dashing start 6 | ExecStop=/etc/init.d/dashing stop 7 | RemainAfterExit=yes 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /init/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | home-assistant: 3 | build: Dockerfile-rpi 4 | restart: always 5 | net: "host" 6 | volumes: 7 | - '/etc/localtime:/etc/localtime:ro' 8 | - '/etc/timezone:/etc/timezone:ro' 9 | - '/etc/home-assistant/hadashboard/dashboards:/app/dashboards 10 | - '/etc/home-assistant/hadashboard/config/:/app/lib/ 11 | - '/etc/home-assistant/hadashboard/hapush/:/app/hapush/ 12 | 13 | -------------------------------------------------------------------------------- /init/hapush: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # HAPUSH Service 3 | # Add this file to /etc/init.d/ 4 | # $ sudo cp hapush /etc/init.d/ 5 | # Update variables PATH to suit your installation 6 | # $ sudo nano /etc/init.d/dashboard 7 | # Make executable 8 | # $ sudo chmod 755 /etc/init.d/dashboard 9 | # Update rc.d 10 | # $ sudo update-rc.d dashboard defaults 11 | # HAPUSH will start at boot. Check out the boot log for trouble shooting "/var/log/boot.log" 12 | # USAGE: start|stop|status|logs 13 | 14 | ### BEGIN INIT INFO 15 | # Provides: hapush 16 | # Required-Start: $remote_fs $syslog 17 | # Required-Stop: $remote_fs $syslog 18 | # Default-Start: 2 3 4 5 19 | # Default-Stop: 0 1 6 20 | # Short-Description: Start daemon at boot time 21 | # Description: Enable service provided by daemon. 22 | ### END INIT INFO 23 | 24 | set -e 25 | 26 | # Must be a valid filename 27 | NAME=hapush 28 | HAPUSH_DIR=/srv/hass/src/hadashboard/hapush 29 | DAEMON="$HAPUSH_DIR/$NAME.py" 30 | PIDFILE="$HAPUSH_DIR/$NAME.pid" 31 | CFGFILE="$HAPUSH_DIR/$NAME.cfg" 32 | DAEMON_OPTS="-d -p $PIDFILE $CFGFILE" 33 | 34 | export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games 35 | 36 | case "$1" in 37 | start) 38 | echo -n "Starting daemon: "$NAME 39 | start-stop-daemon --start --quiet --chdir $HAPUSH_DIR --exec $DAEMON -- $DAEMON_OPTS 40 | echo "." 41 | ;; 42 | stop) 43 | echo -n "Stopping daemon: "$NAME 44 | start-stop-daemon --stop --quiet --signal 9 --oknodo --pidfile $PIDFILE 45 | echo "." 46 | ;; 47 | restart) 48 | echo -n "Restarting daemon: "$NAME 49 | start-stop-daemon --stop --quiet --signal 9 --oknodo --retry 30 --pidfile $PIDFILE 50 | start-stop-daemon --start --quiet --chdir $HAPUSH_DIR --exec $DAEMON -- $DAEMON_OPTS 51 | echo "." 52 | ;; 53 | logs) 54 | echo "See the logs of the Dashing." 55 | tail -f $HAPUSH_DIR'log/thin.log' 56 | ;; 57 | 58 | *) 59 | echo "Usage: "$1" {start|stop|restart}" 60 | exit 1 61 | esac 62 | 63 | exit 0 64 | -------------------------------------------------------------------------------- /init/hapush.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Push events from Home Assistant to Dashing 3 | 4 | [Service] 5 | ExecStart=/etc/init.d/hapush start 6 | ExecStop=/etc/init.d/hapush stop 7 | RemainAfterExit=yes 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /jobs/heartbeat.rb: -------------------------------------------------------------------------------- 1 | # Send a heartbeat message to keep the event stream open 2 | SCHEDULER.every '15s', :first_in => 0 do |job| 3 | event = ":heartbeat #{Time.now.to_i}\n\n" 4 | Sinatra::Application.settings.connections.each { |out| out << event } 5 | end 6 | -------------------------------------------------------------------------------- /jobs/homeassistant.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'net/http' 3 | 4 | $selectedDimmer = {} 5 | $alarm_control_panel_code = "" 6 | 7 | $ha_api = $ha_url + "/api/" 8 | 9 | def ha_api(path, method, parameters={}) 10 | uri = URI.parse($ha_api + path) 11 | http = Net::HTTP.new(uri.host, uri.port) 12 | if uri.scheme == "https" 13 | http.use_ssl = true 14 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 15 | end 16 | if method == "get" 17 | request = Net::HTTP::Get.new(uri.request_uri) 18 | else 19 | request = Net::HTTP::Post.new(uri.request_uri) 20 | request.body = parameters.to_json 21 | end 22 | request.initialize_http_header({"x-ha-access" => $ha_apikey, 'Content-Type' =>'application/json'}) 23 | response = http.request(request) 24 | return JSON.parse(response.body) 25 | end 26 | 27 | def respondWithStatus(status) 28 | response = JSON.generate({"error"=> status}) 29 | return response 30 | end 31 | 32 | def respondWithSuccess() 33 | respondWithStatus(0) 34 | end 35 | 36 | # Dispatch requests to the Home Assistant RESTFull API 37 | 38 | get '/homeassistant/switch' do 39 | response = ha_api("states/switch." + params["widgetId"], "get") 40 | return JSON.generate({"state" => response["state"]}) 41 | end 42 | 43 | post '/homeassistant/switch' do 44 | entity_id = "switch." + params["widgetId"] 45 | ha_api("services/switch/turn_" + params["command"], "post", {"entity_id" => entity_id}) 46 | return respondWithSuccess() 47 | end 48 | 49 | get '/homeassistant/group' do 50 | response = ha_api("states/group." + params["widgetId"], "get") 51 | return JSON.generate({"state" => response["state"]}) 52 | end 53 | 54 | post '/homeassistant/group' do 55 | entity_id = "group." + params["widgetId"] 56 | ha_api("services/homeassistant/turn_" + params["command"], "post", {"entity_id" => entity_id}) 57 | return respondWithSuccess() 58 | end 59 | 60 | get '/homeassistant/garage' do 61 | response = ha_api("states/garage_door." + params["widgetId"], "get") 62 | return JSON.generate({"state" => response["state"]}) 63 | end 64 | 65 | post '/homeassistant/garage' do 66 | entity_id = "garage_door." + params["widgetId"] 67 | command = "close" 68 | if params["command"] == "open" 69 | command = "open" 70 | else 71 | command = "close" 72 | end 73 | ha_api("services/garage_door/" + command, "post", {"entity_id" => entity_id}) 74 | return respondWithSuccess() 75 | end 76 | 77 | get '/homeassistant/cover' do 78 | response = ha_api("states/cover." + params["widgetId"], "get") 79 | return JSON.generate({"state" => response["state"]}) 80 | end 81 | 82 | post '/homeassistant/cover' do 83 | entity_id = "cover." + params["widgetId"] 84 | command = "close_cover" 85 | if params["command"] == "open" 86 | command = "open_cover" 87 | else 88 | command = "close_cover" 89 | end 90 | ha_api("services/cover/" + command, "post", {"entity_id" => entity_id}) 91 | return respondWithSuccess() 92 | end 93 | 94 | 95 | get '/homeassistant/lock' do 96 | response = ha_api("states/lock." + params["widgetId"], "get") 97 | return JSON.generate({"state" => response["state"]}) 98 | end 99 | 100 | post '/homeassistant/lock' do 101 | entity_id = "lock." + params["widgetId"] 102 | command = "lock" 103 | if params["command"] == "unlock" 104 | command = "unlock" 105 | else 106 | command = "lock" 107 | end 108 | ha_api("services/lock/" + command, "post", {"entity_id" => entity_id}) 109 | return respondWithSuccess() 110 | end 111 | 112 | get '/homeassistant/alarm_control_panel_status' do 113 | response = ha_api("states/alarm_control_panel." + params["widgetId"], "get") 114 | return JSON.generate({"value" => response["state"]}) 115 | end 116 | 117 | post '/homeassistant/alarm_control_panel_digit' do 118 | digit = params["digit"] 119 | alarm_entity = params["alarmEntity"] 120 | 121 | if digit == "-" 122 | # the 'clear' button has been pressed 123 | # so blank the stored code and retrieve current status for display 124 | $alarm_control_panel_code = "" 125 | response = ha_api("states/alarm_control_panel." + alarm_entity, "get") 126 | status_widget_value = response["state"] 127 | else 128 | # number has been pressed so add to code 129 | $alarm_control_panel_code = $alarm_control_panel_code + digit 130 | status_widget_value = $alarm_control_panel_code 131 | end 132 | 133 | # Send value back to the alarm status widget 134 | send_event(alarm_entity, { 135 | value: status_widget_value 136 | }) 137 | 138 | return respondWithSuccess() 139 | end 140 | 141 | post '/homeassistant/alarm_control_panel_action' do 142 | # action will be one of 143 | # disarm 144 | # arm_home 145 | # arm_away 146 | # trigger 147 | if $alarm_control_panel_code == "" 148 | ha_api("services/alarm_control_panel/alarm_" + params["action"], "post") 149 | else 150 | ha_api("services/alarm_control_panel/alarm_" + params["action"], "post", {"code" => $alarm_control_panel_code}) 151 | end 152 | # now blank the code 153 | $alarm_control_panel_code = "" 154 | return respondWithSuccess() 155 | end 156 | 157 | get '/homeassistant/script' do 158 | response = ha_api("states/input_select." + params["input"], "get") 159 | return JSON.generate({"mode" => response["state"]}) 160 | end 161 | 162 | post '/homeassistant/script' do 163 | entity_id = "script." + params["widgetId"] 164 | ha_api("services/script/turn_on", "post", {"entity_id" => entity_id}) 165 | return respondWithSuccess() 166 | end 167 | 168 | post '/homeassistant/scene' do 169 | entity_id = "scene." + params["widgetId"] 170 | ha_api("services/scene/turn_on", "post", {"entity_id" => entity_id}) 171 | return respondWithSuccess() 172 | end 173 | 174 | get '/homeassistant/scene' do 175 | response = ha_api("states/scene." + params["widgetId"], "get") 176 | return JSON.generate({"mode" => response["state"]}) 177 | end 178 | 179 | get '/homeassistant/mediaplayer' do 180 | response = ha_api("states/media_player." + params["widgetId"], "get") 181 | return JSON.generate(response) 182 | end 183 | 184 | post '/homeassistant/mediaplayerMute' do 185 | entity_id = "media_player." + params["widgetId"] 186 | ha_api("services/media_player/volume_mute", "post", {"entity_id" => entity_id, 187 | "is_volume_muted" => params['command'] }) 188 | return respondWithSuccess() 189 | end 190 | 191 | post '/homeassistant/mediaplayerVolumeUp' do 192 | entity_id = "media_player." + params["widgetId"] 193 | ha_api("services/media_player/volume_up", "post", {"entity_id" => entity_id}) 194 | return respondWithSuccess() 195 | end 196 | 197 | post '/homeassistant/mediaplayerVolumeDown' do 198 | entity_id = "media_player." + params["widgetId"] 199 | ha_api("services/media_player/volume_down", "post", {"entity_id" => entity_id}) 200 | return respondWithSuccess() 201 | end 202 | 203 | post '/homeassistant/mediaplayerVolumeSet' do 204 | entity_id = "media_player." + params["widgetId"] 205 | ha_api("services/media_player/volume_set", "post", {"entity_id" => entity_id, 206 | "volume_level" => Float(params["command"])}) 207 | return respondWithSuccess() 208 | end 209 | 210 | post '/homeassistant/mediaplayerPlayPause' do 211 | entity_id = "media_player." + params["widgetId"] 212 | ha_api("services/media_player/media_play_pause", "post", {"entity_id" => entity_id}) 213 | return respondWithSuccess() 214 | end 215 | 216 | post '/homeassistant/mediaplayerNext' do 217 | entity_id = "media_player." + params["widgetId"] 218 | ha_api("services/media_player/media_next_track", "post", {"entity_id" => entity_id}) 219 | return respondWithSuccess() 220 | end 221 | 222 | post '/homeassistant/mediaplayerPrev' do 223 | entity_id = "media_player." + params["widgetId"] 224 | ha_api("services/media_player/media_previous_track", "post", {"entity_id" => entity_id}) 225 | return respondWithSuccess() 226 | end 227 | 228 | get '/homeassistant/dimmer' do 229 | response = ha_api("states/light." + params["widgetId"], "get") 230 | if response["brightness"] == nil 231 | level = 30 232 | else 233 | level = Integer(response["brightness"]).round(-1) 234 | end 235 | return JSON.generate({"state" => response["state"], "level" => level}) 236 | end 237 | 238 | post '/homeassistant/dimmer' do 239 | entity_id = "light." + params["widgetId"] 240 | ha_api("services/light/turn_" + params["command"], "post", {"entity_id" => entity_id}) 241 | return respondWithSuccess() 242 | end 243 | 244 | post '/homeassistant/dimmerLevel' do 245 | entity_id = "light." + params["widgetId"] 246 | ha_api("services/light/turn_on", "post", {"entity_id" => entity_id, "brightness" => Integer(params["command"]) * 2.55}) 247 | return respondWithSuccess() 248 | end 249 | 250 | post '/homeassistant/selectdimmer' do 251 | $selectedDimmer[request.ip] = params["widgetId"] 252 | return respondWithSuccess() 253 | end 254 | 255 | post '/homeassistant/setdimmer' do 256 | entity_id = "light." + $selectedDimmer[request.ip] 257 | if params["command"] == "off" 258 | ha_api("services/light/turn_off", "post", {"entity_id" => entity_id}) 259 | else 260 | ha_api("services/light/turn_on", "post", {"entity_id" => entity_id, "brightness" => Integer(params["command"]) * 2.55}) 261 | end 262 | end 263 | 264 | get '/homeassistant/devicetracker' do 265 | response = ha_api("states/device_tracker." + params["widgetId"], "get") 266 | if response["state"] == "not_home" 267 | state = "away" 268 | else 269 | state = response["state"] 270 | end 271 | 272 | return JSON.generate({"state" => state.upcase}) 273 | end 274 | 275 | post '/homeassistant/devicetracker' do 276 | entity_id = params["widgetId"] 277 | state = params["command"].downcase 278 | if state == "away" 279 | state = "not_home" 280 | end 281 | ha_api("services/device_tracker/see", "post", {"dev_id" => entity_id, "location_name" => state}) 282 | return respondWithSuccess() 283 | end 284 | 285 | get '/homeassistant/inputselect' do 286 | response = ha_api("states/input_select." + params["widgetId"], "get") 287 | return JSON.generate({"value" => response["state"]}) 288 | end 289 | 290 | get '/homeassistant/inputboolean' do 291 | response = ha_api("states/input_boolean." + params["widgetId"], "get") 292 | return JSON.generate({"state" => response["state"]}) 293 | end 294 | 295 | post '/homeassistant/inputboolean' do 296 | entity_id = "input_boolean." + params["widgetId"] 297 | ha_api("services/input_boolean/turn_" + params["command"], "post", {"entity_id" => entity_id}) 298 | return respondWithSuccess() 299 | end 300 | 301 | get '/homeassistant/sensor' do 302 | response = ha_api("states/sensor." + params["widgetId"], "get") 303 | return JSON.generate({"value" => response["state"]}) 304 | end 305 | 306 | get '/homeassistant/temperature' do 307 | response = ha_api("states/sensor." + params["widgetId"], "get") 308 | return JSON.generate({"value" => response["state"]}) 309 | end 310 | 311 | get '/homeassistant/lux' do 312 | response = ha_api("states/sensor." + params["widgetId"], "get") 313 | return JSON.generate({"value" => response["state"]}) 314 | end 315 | 316 | get '/homeassistant/binarysensor' do 317 | response = ha_api("states/binary_sensor." + params["widgetId"], "get") 318 | return JSON.generate({"state" => response["state"]}) 319 | end 320 | 321 | get '/homeassistant/humidity' do 322 | response = ha_api("states/sensor." + params["widgetId"], "get") 323 | return JSON.generate({"value" => response["state"]}) 324 | end 325 | 326 | 327 | #Update the weather ever so often 328 | SCHEDULER.every '15m', :first_in => 0 do |job| 329 | #Current weather 330 | response = ha_api("states/sensor.dark_sky_temperature", "get") 331 | temp = response["state"] 332 | 333 | response = ha_api("states/sensor.dark_sky_humidity", "get") 334 | humidity = response["state"] 335 | 336 | response = ha_api("states/sensor.dark_sky_precip_probability", "get") 337 | precip = response["state"] 338 | 339 | response = ha_api("states/sensor.dark_sky_precip_intensity", "get") 340 | precipintensity = response["state"] 341 | 342 | response = ha_api("states/sensor.dark_sky_wind_speed", "get") 343 | windspeed = response["state"] 344 | 345 | response = ha_api("states/sensor.dark_sky_pressure", "get") 346 | pressure = response["state"] 347 | 348 | response = ha_api("states/sensor.dark_sky_wind_bearing", "get") 349 | windbearing = response["state"] 350 | 351 | 352 | response = ha_api("states/sensor.dark_sky_apparent_temperature", "get") 353 | tempchill = response["state"] 354 | 355 | response = ha_api("states/sensor.dark_sky_icon", "get") 356 | icon = response["state"].gsub(/-/, '_') 357 | 358 | #Emit the event 359 | send_event('weather', { 360 | temp: temp, 361 | humidity: humidity, 362 | icon: icon, 363 | tempchill: tempchill, 364 | precipintensity: precipintensity, 365 | precip: precip, 366 | windspeed: windspeed, 367 | windbearing: windbearing, 368 | pressure: pressure 369 | }) 370 | end 371 | -------------------------------------------------------------------------------- /jobs/news.rb: -------------------------------------------------------------------------------- 1 | require 'rss' 2 | require 'open-uri' 3 | require 'nokogiri' 4 | require 'htmlentities' 5 | 6 | Decoder = HTMLEntities.new 7 | 8 | class News 9 | def initialize(widget_id, feed) 10 | @widget_id = widget_id 11 | @feed = feed 12 | end 13 | 14 | def widget_id() 15 | @widget_id 16 | end 17 | 18 | def truncate(string, length = 200) 19 | raise 'Truncate: Length should be greater than 3' unless length > 3 20 | 21 | truncated_string = string.to_s 22 | if truncated_string.length > length 23 | truncated_string = truncated_string[0...(length - 3)] 24 | truncated_string += '...' 25 | end 26 | truncated_string 27 | end 28 | 29 | def latest_headlines() 30 | news_headlines = [] 31 | open(@feed) do |rss| 32 | feed = RSS::Parser.parse(rss) 33 | feed.items.each do |item| 34 | title = clean_html(item.title.to_s) 35 | begin 36 | summary = truncate(clean_html(item.description)) 37 | rescue 38 | doc = Nokogiri::HTML(item.summary.content) 39 | summary = truncate((doc.xpath("//text()").remove).to_s) 40 | end 41 | news_headlines.push({ title: title, description: summary }) 42 | end 43 | end 44 | news_headlines 45 | end 46 | 47 | def clean_html( html ) 48 | html = html.gsub(/<\/?[^>]*>/, "") 49 | html = Decoder.decode( html ) 50 | return html 51 | end 52 | 53 | end 54 | 55 | @News = [] 56 | $news_feeds.each do |widget_id, feed| 57 | begin 58 | @News.push(News.new(widget_id, feed)) 59 | rescue Exception => e 60 | puts e.to_s 61 | end 62 | end 63 | 64 | SCHEDULER.every '60m', :first_in => 0 do |job| 65 | @News.each do |news| 66 | headlines = news.latest_headlines() 67 | send_event(news.widget_id, { :headlines => headlines }) 68 | end 69 | end 70 | 71 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | ha_conf.rb 2 | -------------------------------------------------------------------------------- /lib/ha_conf.rb.example: -------------------------------------------------------------------------------- 1 | $ha_url = "http://192.168.1.10:8123" 2 | $ha_apikey = "ha key" 3 | 4 | $news_feeds = { 5 | "Traffic" => "http://api.sr.se/api/rss/traffic/2863", 6 | "News" => "http://feeds.bbci.co.uk/news/rss.xml", 7 | } 8 | 9 | -------------------------------------------------------------------------------- /lib/settings.rb: -------------------------------------------------------------------------------- 1 | require 'data_mapper' 2 | 3 | # Initialize the DataMapper to use a database, if available. Fall back to an 4 | # sqlite file, if no database has been set up. 5 | DataMapper.setup(:default, ENV['DATABASE_URL'] || 'sqlite:persistent.db') 6 | 7 | # Set object model for settings 8 | class Setting 9 | include DataMapper::Resource 10 | 11 | property :name, String, :key => true 12 | property :value, Text 13 | end 14 | 15 | # Finalize all models 16 | DataMapper.finalize 17 | 18 | # Up-migrate the schema 19 | DataMapper.auto_upgrade! 20 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This Dashboard doesn't exist. 5 | 17 | 18 | 19 | 20 | 21 |
22 |

Drats! That Dashboard doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | -------------------------------------------------------------------------------- /widgets/change_page/change_page.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.ChangePage extends Dashing.Widget 2 | 3 | ready: -> 4 | if @get('bgcolor') 5 | $(@node).css("background-color", @get('bgcolor')) 6 | 7 | onData: (data) -> 8 | 9 | onClick: (node, event) -> 10 | Dashing.cycleDashboardsNow(boardnumber: @get('page'), stagger: @get('stagger'), fastTransition: @get('fasttransition'), transitiontype: @get('transitiontype')) 11 | -------------------------------------------------------------------------------- /widgets/change_page/change_page.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

-------------------------------------------------------------------------------- /widgets/change_page/change_page.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-change-page { 7 | 8 | .container { 9 | position: relative; 10 | height: 75px; 11 | } 12 | 13 | .icon, .timer { 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | width: 100%; 18 | height: 100%; 19 | } 20 | 21 | .timer { 22 | z-index: 1; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /widgets/clock/clock.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Clock extends Dashing.Widget 2 | 3 | ready: -> 4 | if @get('bgcolor') 5 | $(@node).css("background-color", @get('bgcolor')) 6 | setInterval(@startTime, 500) 7 | 8 | startTime: => 9 | today = new Date() 10 | 11 | h = today.getHours() 12 | m = today.getMinutes() 13 | m = @formatTime(m) 14 | @set('time', @formatHours(h) + ":" + m + " " + @formatAmPm(h)) 15 | @set('date', today.toLocaleDateString()) 16 | 17 | formatTime: (i) -> 18 | if i < 10 then "0" + i else i 19 | 20 | formatAmPm: (h) -> 21 | if h >= 12 then "PM" else "AM" 22 | 23 | formatHours: (h) -> 24 | if h > 12 25 | h - 12 26 | else if h == 0 27 | 12 28 | else 29 | h 30 | -------------------------------------------------------------------------------- /widgets/clock/clock.html: -------------------------------------------------------------------------------- 1 |

2 |

-------------------------------------------------------------------------------- /widgets/clock/clock.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-clock styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-clock { 7 | 8 | .time { 9 | color: $purple; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /widgets/comments/comments.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Comments extends Dashing.Widget 2 | 3 | @accessor 'quote', -> 4 | "“#{@get('current_comment')?.body}”" 5 | 6 | ready: -> 7 | @currentIndex = 0 8 | @commentElem = $(@node).find('.comment-container') 9 | @nextComment() 10 | @startCarousel() 11 | 12 | onData: (data) -> 13 | @currentIndex = 0 14 | 15 | startCarousel: -> 16 | setInterval(@nextComment, 8000) 17 | 18 | nextComment: => 19 | comments = @get('comments') 20 | if comments 21 | @commentElem.fadeOut => 22 | @currentIndex = (@currentIndex + 1) % comments.length 23 | @set 'current_comment', comments[@currentIndex] 24 | @commentElem.fadeIn() 25 | -------------------------------------------------------------------------------- /widgets/comments/comments.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |

4 |

5 |
6 | 7 |

8 | -------------------------------------------------------------------------------- /widgets/comments/comments.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #eb9c3c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-comment styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-comments { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | margin-bottom: 15px; 19 | } 20 | 21 | .name { 22 | padding-left: 5px; 23 | } 24 | 25 | .comment-container { 26 | display: none; 27 | } 28 | 29 | .more-info { 30 | color: $moreinfo-color; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /widgets/graph/graph.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Graph extends Dashing.Widget 2 | 3 | @accessor 'current', -> 4 | return @get('displayedValue') if @get('displayedValue') 5 | points = @get('points') 6 | if points 7 | points[points.length - 1].y 8 | 9 | ready: -> 10 | container = $(@node).parent() 11 | # Gross hacks. Let's fix this. 12 | width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1) 13 | height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) 14 | @graph = new Rickshaw.Graph( 15 | element: @node 16 | width: width 17 | height: height 18 | renderer: @get("graphtype") 19 | series: [ 20 | { 21 | color: "#fff", 22 | data: [{x:0, y:0}] 23 | } 24 | ] 25 | ) 26 | 27 | @graph.series[0].data = @get('points') if @get('points') 28 | 29 | x_axis = new Rickshaw.Graph.Axis.Time(graph: @graph) 30 | y_axis = new Rickshaw.Graph.Axis.Y(graph: @graph, tickFormat: Rickshaw.Fixtures.Number.formatKMBT) 31 | @graph.render() 32 | 33 | onData: (data) -> 34 | if @graph 35 | @graph.series[0].data = data.points 36 | @graph.render() 37 | -------------------------------------------------------------------------------- /widgets/graph/graph.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | -------------------------------------------------------------------------------- /widgets/graph/graph.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #dc5945; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | $tick-color: rgba(0, 0, 0, 0.4); 9 | 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-graph styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-graph { 15 | 16 | background-color: $background-color; 17 | position: relative; 18 | 19 | 20 | svg { 21 | position: absolute; 22 | opacity: 0.4; 23 | fill-opacity: 0.4; 24 | left: 0px; 25 | top: 0px; 26 | } 27 | 28 | .title, .value { 29 | position: relative; 30 | z-index: 99; 31 | } 32 | 33 | .title { 34 | color: $title-color; 35 | } 36 | 37 | .more-info { 38 | color: $moreinfo-color; 39 | font-weight: 600; 40 | font-size: 100%; 41 | margin-top: 0; 42 | } 43 | 44 | .x_tick { 45 | position: absolute; 46 | bottom: 0; 47 | .title { 48 | font-size: 100%; 49 | color: $tick-color; 50 | opacity: 0.5; 51 | padding-bottom: 3px; 52 | } 53 | } 54 | 55 | .y_ticks { 56 | font-size: 100%; 57 | fill: $tick-color; 58 | fill-opacity: 1; 59 | } 60 | 61 | .domain { 62 | display: none; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /widgets/haalarmaction/haalarmaction.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Haalarmaction extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | 5 | state = 'off' 6 | 7 | @accessor 'icon', 8 | get: -> @['icon'] ? 'tag' 9 | set: Batman.Property.defaultAccessor.set 10 | 11 | @accessor 'icon-style', 12 | get: -> if state == 'on' then 'icon-active' else 'icon-inactive' 13 | set: Batman.Property.defaultAccessor.set 14 | 15 | @accessor 'title-style', 16 | get: -> if state == 'on' then 'title-active' else 'title-inactive' 17 | set: Batman.Property.defaultAccessor.set 18 | 19 | ready: -> 20 | if @get('bgcolor') 21 | $(@node).css("background-color", @get('bgcolor')) 22 | 23 | turnOff: => 24 | state = 'off' 25 | @set 'icon-style', 'icon-inactive' 26 | @set 'title-style', 'title-inactive' 27 | 28 | postScene: -> 29 | $.post '/homeassistant/alarm_control_panel_action', 30 | action: @get('action'), 31 | (data) => 32 | json = JSON.parse data 33 | if json.error != 0 34 | @toggleState() 35 | 36 | onClick: (event) -> 37 | @postScene() 38 | state = 'on' 39 | @set 'icon-style', 'icon-active' 40 | @set 'title-style', 'title-active' 41 | @_timeout = setTimeout(@turnOff, @['ontime'] ? 1000) 42 | -------------------------------------------------------------------------------- /widgets/haalarmaction/haalarmaction.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |
4 | 5 |
6 |
7 |

8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /widgets/haalarmaction/haalarmaction.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-stmode styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-haalarmaction { 7 | 8 | .title { 9 | padding-top: 10px; 10 | font-size: 225%; 11 | font-weight: 500; 12 | color: $gray-light; 13 | } 14 | 15 | .container { 16 | position: relative; 17 | height: 65px; 18 | } 19 | 20 | .icon { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | .title-inactive { 29 | color: $gray-light; 30 | } 31 | 32 | .title-active { 33 | color: $color-active; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /widgets/haalarmdigit/haalarmdigit.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Haalarmdigit extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | 5 | state = 'off' 6 | 7 | @accessor 'icon', 8 | get: -> @['icon'] ? 'stop' 9 | set: Batman.Property.defaultAccessor.set 10 | 11 | @accessor 'icon-style', 12 | get: -> if state == 'on' then 'icon-active' else 'icon-inactive' 13 | set: Batman.Property.defaultAccessor.set 14 | 15 | @accessor 'title-style', 16 | get: -> if state == 'on' then 'title-active' else 'title-inactive' 17 | set: Batman.Property.defaultAccessor.set 18 | 19 | ready: -> 20 | if @get('bgcolor') 21 | $(@node).css("background-color", @get('bgcolor')) 22 | 23 | turnOff: => 24 | state = 'off' 25 | @set 'icon-style', 'icon-inactive' 26 | @set 'title-style', 'title-inactive' 27 | 28 | postScene: -> 29 | $.post '/homeassistant/alarm_control_panel_digit', 30 | digit: @get('digit'), 31 | alarmEntity: @get('alarmentity'), 32 | (data) => 33 | json = JSON.parse data 34 | if json.error != 0 35 | @toggleState() 36 | 37 | onClick: (event) -> 38 | @postScene() 39 | state = 'on' 40 | @set 'icon-style', 'icon-active' 41 | @set 'title-style', 'title-active' 42 | 43 | @_timeout = setTimeout(@turnOff, @['ontime'] ? 700) 44 | -------------------------------------------------------------------------------- /widgets/haalarmdigit/haalarmdigit.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |
4 | 5 |
6 |
7 |

8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /widgets/haalarmdigit/haalarmdigit.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-stmode styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-haalarmdigit { 7 | 8 | .title { 9 | padding-top: 10px; 10 | font-size: 225%; 11 | font-weight: 500; 12 | color: $gray-light; 13 | } 14 | 15 | .container { 16 | position: relative; 17 | height: 65px; 18 | } 19 | 20 | .icon { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | .title-inactive { 29 | color: $color-inactive; 30 | } 31 | 32 | .title-active { 33 | color: $color-active; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /widgets/haalarmstatus/haalarmstatus.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Haalarmstatus extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'value', 7 | get: -> @_value ? 'UNKNOWN' 8 | set: (key, value) -> 9 | if value == "disarmed" 10 | @_value = "DISARMED" 11 | else if value == "pending" 12 | @_value = "PENDING" 13 | else if value == "armed_home" 14 | @_value = "ARMED (HOME)" 15 | else if value == "armed_away" 16 | @_value = "ARMED (AWAY)" 17 | else if value == "triggered" 18 | @_value = "TRIGGERED" 19 | else 20 | @_value = value 21 | 22 | queryState: -> 23 | $.get '/homeassistant/alarm_control_panel_status', 24 | widgetId: @get('id'), 25 | (data) => 26 | json = JSON.parse data 27 | @set 'value', json.value 28 | 29 | ready: -> 30 | if @get('bgcolor') 31 | $(@node).css("background-color", @get('bgcolor')) 32 | 33 | onData: (data) -> 34 | -------------------------------------------------------------------------------- /widgets/haalarmstatus/haalarmstatus.html: -------------------------------------------------------------------------------- 1 |

2 | -------------------------------------------------------------------------------- /widgets/haalarmstatus/haalarmstatus.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-haalarmstatus styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-haalarmstatus { 7 | 8 | .value { 9 | color: $white; 10 | font-size: 500%; 11 | font-weight: 400; 12 | display: inline-block; 13 | vertical-align: middle; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /widgets/habinary/habinary.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Habinary extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? "off" 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'icon', 11 | get: -> if @['icon'] then @['icon'] else 12 | if @get('state') == 'on' then @get('iconon') else @get('iconoff') 13 | set: Batman.Property.defaultAccessor.set 14 | 15 | @accessor 'iconon', 16 | get: -> @['iconon'] ? 'bullseye' 17 | set: Batman.Property.defaultAccessor.set 18 | 19 | @accessor 'iconoff', 20 | get: -> @['iconoff'] ? 'minus' 21 | set: Batman.Property.defaultAccessor.set 22 | 23 | @accessor 'icon-style', -> 24 | if @get('state') == 'on' then 'icon-active' else 'icon-inactive' 25 | 26 | queryState: -> 27 | $.get '/homeassistant/binarysensor', 28 | widgetId: @get('id') 29 | (data) => 30 | json = JSON.parse data 31 | @set 'state', json.state 32 | 33 | ready: -> 34 | if @get('bgcolor') 35 | $(@node).css("background-color", @get('bgcolor')) 36 | -------------------------------------------------------------------------------- /widgets/habinary/habinary.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | -------------------------------------------------------------------------------- /widgets/habinary/habinary.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .widget-habinary { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /widgets/hacover/hacover.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hacover extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'open' 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'icon', 11 | get: -> 'car' 12 | set: Batman.Property.defaultAccessor.set 13 | 14 | @accessor 'icon-style', -> 15 | if @get('state') == 'open' then icon = 'icon-open' 16 | if @get('state') == 'closed' then icon = 'icon-closed' 17 | if @get('state') == 'opening' then icon = 'icon-opening' 18 | if @get('state') == 'closing' then icon = 'icon-closing' 19 | return icon 20 | 21 | toggleState: -> 22 | newState = if @get('state') == 'open' then 'closed' else 'open' 23 | @set 'state', newState 24 | return newState 25 | 26 | queryState: -> 27 | $.get '/homeassistant/cover', 28 | widgetId: @get('id'), 29 | (data) => 30 | json = JSON.parse data 31 | @set 'state', json.state 32 | 33 | postState: -> 34 | newState = @toggleState() 35 | $.post '/homeassistant/cover', 36 | widgetId: @get('id'), 37 | command: newState, 38 | (data) => 39 | json = JSON.parse data 40 | if json.error != 0 41 | @toggleState() 42 | 43 | ready: -> 44 | if @get('bgcolor') 45 | $(@node).css("background-color", @get('bgcolor')) 46 | 47 | onData: (data) -> 48 | 49 | onClick: (event) -> 50 | @postState() 51 | -------------------------------------------------------------------------------- /widgets/hacover/hacover.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | -------------------------------------------------------------------------------- /widgets/hacover/hacover.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-hacover styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hacover { 7 | 8 | .icon-open { 9 | color: $red; 10 | } 11 | .icon-opening { 12 | color: $yellow; 13 | } 14 | .icon-closing { 15 | color: $yellow; 16 | } 17 | 18 | .icon-closed { 19 | color: $color-inactive; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /widgets/hadevicetracker/hadevicetracker.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hadevicetracker extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'off' 8 | set: (key, value) -> @_state = value 9 | 10 | format_state: -> 11 | 12 | 13 | @accessor 'icon', 14 | get: -> if @['icon'] then @['icon'] else 15 | if @get('state') == 'HOME' then @get('iconon') else @get('iconoff') 16 | set: Batman.Property.defaultAccessor.set 17 | 18 | @accessor 'iconon', 19 | get: -> @['iconon'] ? 'user' 20 | set: Batman.Property.defaultAccessor.set 21 | 22 | @accessor 'iconoff', 23 | get: -> @['iconoff'] ? 'times' 24 | set: Batman.Property.defaultAccessor.set 25 | 26 | @accessor 'icon-style', -> 27 | if @get('state') == 'HOME' then 'icon-present' else 'icon-absent' 28 | 29 | toggleState: -> 30 | newState = if @get('state') == 'HOME' then 'AWAY' else 'HOME' 31 | @set 'state', newState 32 | return newState 33 | 34 | queryState: -> 35 | $.get '/homeassistant/devicetracker', 36 | widgetId: @get('id'), 37 | (data) => 38 | json = JSON.parse data 39 | @set 'state', json.state 40 | 41 | postState: -> 42 | newState = @toggleState() 43 | $.post '/homeassistant/devicetracker', 44 | widgetId: @get('id'), 45 | command: newState, 46 | (data) => 47 | json = JSON.parse data 48 | if json.error != 0 49 | @toggleState() 50 | 51 | ready: -> 52 | if @get('bgcolor') 53 | $(@node).css("background-color", @get('bgcolor')) 54 | 55 | onClick: (event) -> 56 | @postState() 57 | -------------------------------------------------------------------------------- /widgets/hadevicetracker/hadevicetracker.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 | -------------------------------------------------------------------------------- /widgets/hadevicetracker/hadevicetracker.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-hadevicetracker styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hadevicetracker { 7 | 8 | .icon-present { 9 | color: $color-active; 10 | } 11 | 12 | .icon-absent { 13 | color: $color-inactive; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /widgets/hadimmer/hadimmer.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hadimmer extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'off' 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'level', 11 | get: -> @_level ? '50' 12 | set: (key, value) -> @_level = value 13 | 14 | @accessor 'icon', 15 | get: -> if @['icon'] then @['icon'] else 16 | if @get('state') == 'on' then @get('iconon') else @get('iconoff') 17 | set: Batman.Property.defaultAccessor.set 18 | 19 | @accessor 'iconon', 20 | get: -> @['iconon'] ? 'circle' 21 | set: Batman.Property.defaultAccessor.set 22 | 23 | @accessor 'iconoff', 24 | get: -> @['iconoff'] ? 'circle-thin' 25 | set: Batman.Property.defaultAccessor.set 26 | 27 | @accessor 'icon-style', -> 28 | if @get('state') == 'on' then 'dimmer-icon-on' else 'dimmer-icon-off' 29 | 30 | plusLevel: -> 31 | newLevel = parseInt(@get('level'))+10 32 | if newLevel > 100 33 | newLevel = 100 34 | else if newLevel < 0 35 | newLevel = 0 36 | @set 'level', newLevel 37 | return @get('level') 38 | 39 | minusLevel: -> 40 | newLevel = parseInt(@get('level'))-10 41 | if newLevel > 100 42 | newLevel = 100 43 | else if newLevel < 0 44 | newLevel = 0 45 | @set 'level', newLevel 46 | return @get('level') 47 | 48 | toggleState: -> 49 | newState = if @get('state') == 'on' then 'off' else 'on' 50 | @set 'state', newState 51 | return newState 52 | 53 | queryState: -> 54 | $.get '/homeassistant/dimmer', 55 | widgetId: @get('id'), 56 | (data) => 57 | json = JSON.parse data 58 | @set 'state', json.state 59 | @set 'level', json.level 60 | 61 | postState: -> 62 | newState = @toggleState() 63 | $.post '/homeassistant/dimmer', 64 | widgetId: @get('id'), 65 | command: newState, 66 | (data) => 67 | json = JSON.parse data 68 | if json.error != 0 69 | @toggleState() 70 | 71 | 72 | levelUp: -> 73 | newLevel = @plusLevel() 74 | $.post '/homeassistant/dimmerLevel', 75 | widgetId: @get('id'), 76 | command: newLevel, 77 | (data) => 78 | json = JSON.parse data 79 | 80 | 81 | levelDown: -> 82 | newLevel = @minusLevel() 83 | $.post '/homeassistant/dimmerLevel', 84 | widgetId: @get('id'), 85 | command: newLevel, 86 | (data) => 87 | json = JSON.parse data 88 | 89 | ready: -> 90 | if @get('bgcolor') 91 | $(@node).css("background-color", @get('bgcolor')) 92 | 93 | onData: (data) -> 94 | 95 | onClick: (event) -> 96 | if event.target.id == "level-down" 97 | @levelDown() 98 | else if event.target.id == "level-up" 99 | @levelUp() 100 | else if event.target.id == "switch" 101 | @postState() 102 | -------------------------------------------------------------------------------- /widgets/hadimmer/hadimmer.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 |

%

7 |

8 |

-------------------------------------------------------------------------------- /widgets/hadimmer/hadimmer.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hadimmer { 7 | 8 | position: relative; 9 | 10 | .title { 11 | margin-bottom: 0; 12 | margin-top: 10px; 13 | } 14 | 15 | .dimmer-icon-off .dimmer-icon-on { 16 | font-size: 150%; 17 | } 18 | 19 | .dimmer-icon-off { 20 | color: $color-inactive; 21 | } 22 | 23 | .dimmer-icon-on { 24 | color: $color-active; 25 | } 26 | 27 | .toggle-area { 28 | z-index: 10; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | width: 100%; 33 | height: 100px; 34 | } 35 | 36 | .level { 37 | display: inline-block; 38 | margin-bottom: 5px; 39 | color: rgba(255, 255, 255, 0.7); 40 | } 41 | 42 | .unit { 43 | display: inline-block; 44 | color: rgba(255, 255, 255, 0.7); 45 | } 46 | 47 | .secondary-icon { 48 | position: absolute; 49 | bottom: 0px; 50 | font-size: 25px; 51 | width: 32px; 52 | color: $color-inactive; 53 | 54 | &.plus { 55 | right: 24px; 56 | 57 | i { 58 | padding-top: 10px; 59 | padding-left: 30px; 60 | } 61 | } 62 | 63 | &.minus { 64 | left: 8px; 65 | 66 | i { 67 | padding-top: 10px; 68 | padding-right: 30px; 69 | } 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /widgets/hagarage/hagarage.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hagarage extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'open' 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'icon', 11 | get: -> 'car' 12 | set: Batman.Property.defaultAccessor.set 13 | 14 | @accessor 'icon-style', -> 15 | if @get('state') == 'open' then icon = 'icon-open' 16 | if @get('state') == 'closed' then icon = 'icon-closed' 17 | if @get('state') == 'opening' then icon = 'icon-opening' 18 | if @get('state') == 'closing' then icon = 'icon-closing' 19 | return icon 20 | 21 | toggleState: -> 22 | newState = if @get('state') == 'open' then 'closed' else 'open' 23 | @set 'state', newState 24 | return newState 25 | 26 | queryState: -> 27 | $.get '/homeassistant/garage', 28 | widgetId: @get('id'), 29 | (data) => 30 | json = JSON.parse data 31 | @set 'state', json.state 32 | 33 | postState: -> 34 | newState = @toggleState() 35 | $.post '/homeassistant/garage', 36 | widgetId: @get('id'), 37 | command: newState, 38 | (data) => 39 | json = JSON.parse data 40 | if json.error != 0 41 | @toggleState() 42 | 43 | ready: -> 44 | if @get('bgcolor') 45 | $(@node).css("background-color", @get('bgcolor')) 46 | 47 | onData: (data) -> 48 | 49 | onClick: (event) -> 50 | @postState() 51 | -------------------------------------------------------------------------------- /widgets/hagarage/hagarage.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | -------------------------------------------------------------------------------- /widgets/hagarage/hagarage.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-hagarage styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hagarage { 7 | 8 | .icon-open { 9 | color: $red; 10 | } 11 | .icon-opening { 12 | color: $yellow; 13 | } 14 | .icon-closing { 15 | color: $yellow; 16 | } 17 | 18 | .icon-closed { 19 | color: $color-inactive; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /widgets/hagroup/hagroup.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hagroup extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'off' 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'icon', 11 | get: -> if @['icon'] then @['icon'] else 12 | if @get('state') == 'on' then @get('iconon') else @get('iconoff') 13 | set: Batman.Property.defaultAccessor.set 14 | 15 | @accessor 'iconon', 16 | get: -> @['iconon'] ? 'circle' 17 | set: Batman.Property.defaultAccessor.set 18 | 19 | @accessor 'iconoff', 20 | get: -> @['iconoff'] ? 'circle-thin' 21 | set: Batman.Property.defaultAccessor.set 22 | 23 | @accessor 'icon-style', -> 24 | if @get('state') == 'on' then 'switch-icon-on' else 'switch-icon-off' 25 | 26 | toggleState: -> 27 | newState = if @get('state') == 'on' then 'off' else 'on' 28 | @set 'state', newState 29 | return newState 30 | 31 | queryState: -> 32 | $.get '/homeassistant/group', 33 | widgetId: @get('id'), 34 | (data) => 35 | json = JSON.parse data 36 | @set 'state', json.state 37 | 38 | postState: -> 39 | newState = @toggleState() 40 | $.post '/homeassistant/group', 41 | widgetId: @get('id'), 42 | command: newState, 43 | (data) => 44 | json = JSON.parse data 45 | if json.error != 0 46 | @toggleState() 47 | 48 | ready: -> 49 | if @get('bgcolor') 50 | $(@node).css("background-color", @get('bgcolor')) 51 | 52 | onClick: (event) -> 53 | @postState() 54 | -------------------------------------------------------------------------------- /widgets/hagroup/hagroup.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | -------------------------------------------------------------------------------- /widgets/hagroup/hagroup.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hagroup { 7 | 8 | .switch-icon-off .switch-icon-on { 9 | font-size: 150%; 10 | } 11 | 12 | .switch-icon-off { 13 | color: $color-inactive; 14 | } 15 | 16 | .switch-icon-on { 17 | color: $color-active; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /widgets/hahumidity/hahumidity.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hahumidity extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'value', 7 | get: -> if @_value then Math.floor(@_value) else 0 8 | set: (key, value) -> @_value = value 9 | 10 | queryState: -> 11 | $.get '/homeassistant/humidity', 12 | widgetId: @get('id'), 13 | (data) => 14 | json = JSON.parse data 15 | @set 'value', json.value 16 | 17 | ready: -> 18 | 19 | onData: (data) -> 20 | -------------------------------------------------------------------------------- /widgets/hahumidity/hahumidity.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

%

5 | -------------------------------------------------------------------------------- /widgets/hahumidity/hahumidity.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-hahumidity styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hahumidity { 7 | 8 | .value { 9 | color: $blue; 10 | display: inline-block; 11 | vertical-align: middle; 12 | } 13 | 14 | .unit { 15 | color: #00aaff; 16 | font-size: 225%; 17 | font-weight: 400; 18 | display: inline-block; 19 | vertical-align: top; 20 | margin-left: 5px; 21 | margin-top: 5px; 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /widgets/hahumiditymeter/hahumiditymeter.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hahumiditymeter extends Dashing.Widget 2 | 3 | @accessor 'value', 4 | get: -> if @_value then Math.round(@_value) else 0 5 | set: (key, value) -> @_value = value 6 | Dashing.AnimatedValue 7 | 8 | constructor: -> 9 | super 10 | @queryState() 11 | @observe 'value', (value) -> 12 | $(@node).find(".meter").val(value).trigger('change') 13 | 14 | queryState: -> 15 | $.get '/homeassistant/humidity', 16 | widgetId: @get('id'), 17 | deviceId: @get('device') 18 | (data) => 19 | json = JSON.parse data 20 | @set 'value', json.value 21 | 22 | ready: -> 23 | meter = $(@node).find(".meter") 24 | meter.attr("data-bgcolor", meter.css("background-color")) 25 | meter.attr("data-fgcolor", meter.css("color")) 26 | meter.knob() 27 | -------------------------------------------------------------------------------- /widgets/hahumiditymeter/hahumiditymeter.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

%

8 | 9 |

10 | -------------------------------------------------------------------------------- /widgets/hahumiditymeter/hahumiditymeter.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | @import "variables"; 5 | $moreinfo-color: rgba(255, 255, 255, 0.3); 6 | 7 | // ---------------------------------------------------------------------------- 8 | // Widget-meter styles 9 | // ---------------------------------------------------------------------------- 10 | .widget-hahumiditymeter { 11 | 12 | position: relative; 13 | 14 | input.meter { 15 | background-color: $gray-medium; 16 | color: $blue; 17 | margin-top: 20px !important; 18 | font-size: 22px !important; 19 | } 20 | 21 | .title { 22 | margin-bottom: 10px; 23 | margin-top: 5px; 24 | } 25 | 26 | .more-info { 27 | color: $moreinfo-color; 28 | } 29 | 30 | .units { 31 | color: rgba(255, 255, 255, 0.7); 32 | font-size: 60%; 33 | position: absolute; 34 | bottom: 30px; 35 | left: 0; 36 | right: 0; 37 | } 38 | 39 | .updated-at { 40 | color: rgba(255, 255, 255, 0.7); 41 | bottom: 5px; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /widgets/hainputboolean/hainputboolean.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hainputboolean extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'off' 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'icon', 11 | get: -> if @['icon'] then @['icon'] else 12 | if @get('state') == 'on' then @get('iconon') else @get('iconoff') 13 | set: Batman.Property.defaultAccessor.set 14 | 15 | @accessor 'iconon', 16 | get: -> @['iconon'] ? 'circle' 17 | set: Batman.Property.defaultAccessor.set 18 | 19 | @accessor 'iconoff', 20 | get: -> @['iconoff'] ? 'circle-thin' 21 | set: Batman.Property.defaultAccessor.set 22 | 23 | @accessor 'icon-style', -> 24 | if @get('state') == 'on' then 'switch-icon-on' else 'switch-icon-off' 25 | 26 | toggleState: -> 27 | newState = if @get('state') == 'on' then 'off' else 'on' 28 | @set 'state', newState 29 | return newState 30 | 31 | queryState: -> 32 | $.get '/homeassistant/inputboolean', 33 | widgetId: @get('id'), 34 | (data) => 35 | json = JSON.parse data 36 | @set 'state', json.state 37 | 38 | postState: -> 39 | newState = @toggleState() 40 | $.post '/homeassistant/inputboolean', 41 | widgetId: @get('id'), 42 | command: newState, 43 | (data) => 44 | json = JSON.parse data 45 | if json.error != 0 46 | @toggleState() 47 | 48 | ready: -> 49 | if @get('bgcolor') 50 | $(@node).css("background-color", @get('bgcolor')) 51 | 52 | onClick: (event) -> 53 | @postState() 54 | -------------------------------------------------------------------------------- /widgets/hainputboolean/hainputboolean.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

-------------------------------------------------------------------------------- /widgets/hainputboolean/hainputboolean.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hainputboolean { 7 | 8 | 9 | .switch-icon-off .switch-icon-on { 10 | font-size: 150%; 11 | } 12 | 13 | .switch-icon-off { 14 | color: $color-inactive; 15 | } 16 | 17 | .switch-icon-on { 18 | color: $color-active; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /widgets/hainputselect/hainputselect.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hainputselect extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'value', 7 | get: -> @_value ? "Unknown" 8 | set: (key, value) -> @_value = value 9 | 10 | queryState: -> 11 | $.get '/homeassistant/inputselect', 12 | widgetId: @get('id'), 13 | (data) => 14 | json = JSON.parse data 15 | @set 'value', json.value 16 | 17 | ready: -> 18 | if @get('bgcolor') 19 | $(@node).css("background-color", @get('bgcolor')) 20 | 21 | onData: (data) -> 22 | -------------------------------------------------------------------------------- /widgets/hainputselect/hainputselect.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | -------------------------------------------------------------------------------- /widgets/hainputselect/hainputselect.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-hainputselect styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hainputselect { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /widgets/halock/halock.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Halock extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'unlocked' 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'icon', 11 | get: -> if @get('state') == 'unlocked' then 'unlock-alt' else 'lock' 12 | set: Batman.Property.defaultAccessor.set 13 | 14 | @accessor 'icon-style', -> 15 | if @get('state') == 'locked' then 'icon-locked' else 'icon-unlocked' 16 | 17 | toggleState: -> 18 | newState = if @get('state') == 'locked' then 'unlock' else 'lock' 19 | @set 'state', newState 20 | return newState 21 | 22 | queryState: -> 23 | $.get '/homeassistant/lock', 24 | widgetId: @get('id'), 25 | (data) => 26 | json = JSON.parse data 27 | @set 'state', json.state 28 | 29 | postState: -> 30 | newState = @toggleState() 31 | $.post '/homeassistant/lock', 32 | widgetId: @get('id'), 33 | command: newState, 34 | (data) => 35 | json = JSON.parse data 36 | if json.error != 0 37 | @toggleState() 38 | ready: -> 39 | if @get('bgcolor') 40 | $(@node).css("background-color", @get('bgcolor')) 41 | 42 | onData: (data) -> 43 | 44 | onClick: (event) -> 45 | @postState() 46 | -------------------------------------------------------------------------------- /widgets/halock/halock.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | -------------------------------------------------------------------------------- /widgets/halock/halock.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-halock { 7 | 8 | .icon-unlocked { 9 | color: $red; 10 | } 11 | 12 | .icon-locked { 13 | color: $white; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /widgets/halux/halux.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Halux extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'value', 7 | get: -> if @_value then Math.floor(@_value) else 0 8 | set: (key, value) -> @_value = value 9 | 10 | queryState: -> 11 | $.get '/homeassistant/lux', 12 | widgetId: @get('id'), 13 | deviceId: @get('device') 14 | (data) => 15 | json = JSON.parse data 16 | @set 'value', json.value 17 | 18 | ready: -> 19 | 20 | onData: (data) -> 21 | -------------------------------------------------------------------------------- /widgets/halux/halux.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

lux

5 | -------------------------------------------------------------------------------- /widgets/halux/halux.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-halux styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-halux { 7 | 8 | .value { 9 | color: $blue; 10 | display: inline-block; 11 | vertical-align: middle; 12 | } 13 | 14 | .unit { 15 | color: $blue; 16 | font-size: 225%; 17 | font-weight: 400; 18 | display: inline-block; 19 | vertical-align: top; 20 | margin-left: 5px; 21 | margin-top: 5px; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /widgets/hamediaplayer/hamediaplayer.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hamediaplayer extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'off' 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'level', 11 | get: -> @_level ? '50' 12 | set: (key, value) -> @_level = value 13 | 14 | @accessor 'icon', 15 | get: -> if @['icon'] then @['icon'] else 16 | if @get('state') == 'playing' 17 | return @get('iconplay') 18 | else if @get('state') == 'paused' 19 | return @get('iconpause') 20 | else 21 | return @get('iconstop') 22 | set: Batman.Property.defaultAccessor.set 23 | 24 | @accessor 'iconplay', 25 | get: -> @['iconplay'] ? 'play' 26 | set: Batman.Property.defaultAccessor.set 27 | 28 | @accessor 'iconpause', 29 | get: -> @['iconpause'] ? 'pause' 30 | set: Batman.Property.defaultAccessor.set 31 | 32 | @accessor 'iconstop', 33 | get: -> @['iconstop'] ? 'stop' 34 | set: Batman.Property.defaultAccessor.set 35 | 36 | @accessor 'icon-style', -> 37 | if @get('state') == 'playing' then 'dimmer-icon-on' else 'dimmer-icon-off' 38 | 39 | plusLevel: -> 40 | newLevel = parseInt(@get('level'))+1 41 | if newLevel > 100 42 | newLevel = 100 43 | else if newLevel < 0 44 | newLevel = 0 45 | @set 'level', newLevel 46 | return @get('level') 47 | 48 | minusLevel: -> 49 | newLevel = parseInt(@get('level'))-1 50 | if newLevel > 100 51 | newLevel = 100 52 | else if newLevel < 0 53 | newLevel = 0 54 | @set 'level', newLevel 55 | return @get('level') 56 | 57 | 58 | queryState: -> 59 | $.get '/homeassistant/mediaplayer', 60 | widgetId: @get('id'), 61 | (data) => 62 | json = JSON.parse data 63 | console.log json 64 | @set 'state', json.state 65 | @set 'level', Math.round(json.attributes.volume_level * 100) 66 | 67 | playPause: -> 68 | $.post '/homeassistant/mediaplayerPlayPause', 69 | widgetId: @get('id'), 70 | (data) => 71 | json = JSON.parse data 72 | if json.error != 0 73 | @toggleState() 74 | 75 | 76 | levelUp: -> 77 | newLevel = @plusLevel() 78 | console.log newLevel / 100.0 79 | $.post '/homeassistant/mediaplayerVolumeSet', 80 | widgetId: @get('id'), 81 | command: newLevel / 100.0, 82 | (data) => 83 | json = JSON.parse data 84 | 85 | 86 | levelDown: -> 87 | newLevel = @minusLevel() 88 | console.log newLevel / 100.0 89 | $.post '/homeassistant/mediaplayerVolumeSet', 90 | widgetId: @get('id'), 91 | command: newLevel / 100.0, 92 | (data) => 93 | json = JSON.parse data 94 | 95 | ready: -> 96 | if @get('bgcolor') 97 | $(@node).css("background-color", @get('bgcolor')) 98 | 99 | onData: (data) -> 100 | console.log "data", data 101 | 102 | onClick: (event) -> 103 | if event.target.id == "level-down" 104 | @levelDown() 105 | else if event.target.id == "level-up" 106 | @levelUp() 107 | else if event.target.id == "switch" 108 | @playPause() 109 | -------------------------------------------------------------------------------- /widgets/hamediaplayer/hamediaplayer.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 |

%

7 |

8 |

-------------------------------------------------------------------------------- /widgets/hamediaplayer/hamediaplayer.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hamediaplayer { 7 | 8 | position: relative; 9 | 10 | .title { 11 | margin-bottom: 0; 12 | margin-top: 10px; 13 | } 14 | 15 | .dimmer-icon-off .dimmer-icon-on { 16 | font-size: 150%; 17 | } 18 | 19 | .dimmer-icon-off { 20 | color: $color-inactive; 21 | } 22 | 23 | .dimmer-icon-on { 24 | color: $color-active; 25 | } 26 | 27 | .toggle-area { 28 | z-index: 10; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | width: 100%; 33 | height: 100px; 34 | } 35 | 36 | .level { 37 | display: inline-block; 38 | margin-bottom: 5px; 39 | color: rgba(255, 255, 255, 0.7); 40 | } 41 | 42 | .unit { 43 | display: inline-block; 44 | color: rgba(255, 255, 255, 0.7); 45 | } 46 | 47 | .secondary-icon { 48 | position: absolute; 49 | bottom: 0px; 50 | font-size: 25px; 51 | width: 32px; 52 | color: $color-inactive; 53 | 54 | &.plus { 55 | right: 24px; 56 | 57 | i { 58 | padding-top: 10px; 59 | padding-left: 30px; 60 | } 61 | } 62 | 63 | &.minus { 64 | left: 8px; 65 | 66 | i { 67 | padding-top: 10px; 68 | padding-right: 30px; 69 | } 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /widgets/hameter/hameter.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hameter extends Dashing.Widget 2 | 3 | @accessor 'value', 4 | get: -> if @_value then Math.round(@_value) else 0 5 | set: (key, value) -> @_value = value 6 | Dashing.AnimatedValue 7 | 8 | constructor: -> 9 | super 10 | @queryState() 11 | @observe 'value', (value) -> 12 | $(@node).find(".meter").val(value).trigger('change') 13 | 14 | queryState: -> 15 | $.get '/homeassistant/sensor', 16 | widgetId: @get('id'), 17 | deviceId: @get('device') 18 | (data) => 19 | json = JSON.parse data 20 | @set 'value', json.value 21 | 22 | ready: -> 23 | if @get('bgcolor') 24 | $(@node).css("background-color", @get('bgcolor')) 25 | meter = $(@node).find(".meter") 26 | meter.attr("data-bgcolor", meter.css("background-color")) 27 | meter.attr("data-fgcolor", meter.css("color")) 28 | meter.knob() 29 | -------------------------------------------------------------------------------- /widgets/hameter/hameter.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 |

10 | -------------------------------------------------------------------------------- /widgets/hameter/hameter.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | $moreinfo-color: rgba(255, 255, 255, 0.3); 3 | 4 | // ---------------------------------------------------------------------------- 5 | // Widget-meter styles 6 | // ---------------------------------------------------------------------------- 7 | .widget-hameter { 8 | 9 | position: relative; 10 | 11 | input.meter { 12 | background-color: $gray-medium; 13 | color: $blue; 14 | margin-top: 20px !important; 15 | font-size: 22px !important; 16 | } 17 | 18 | .title { 19 | color: #fff; 20 | margin-bottom: 10px; 21 | margin-top: 5px; 22 | } 23 | 24 | .more-info { 25 | color: $moreinfo-color; 26 | } 27 | 28 | .units { 29 | color: rgba(255, 255, 255, 0.7); 30 | font-size: 60%; 31 | position: absolute; 32 | bottom: 30px; 33 | left: 0; 34 | right: 0; 35 | } 36 | 37 | .updated-at { 38 | color: rgba(255, 255, 255, 0.7); 39 | bottom: 5px; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /widgets/hamode/hamode.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hamode extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'icon', 7 | get: -> @['icon'] ? 'tag' 8 | set: Batman.Property.defaultAccessor.set 9 | 10 | @accessor 'icon-style', -> 11 | if @isModeSet() then 'icon-active' else 'icon-inactive' 12 | 13 | @accessor 'mode', 14 | get: -> @_mode ? 'Unknown' 15 | set: (key, value) -> @_mode = value 16 | 17 | @accessor 'countdown', 18 | get: -> @_countdown ? 0 19 | set: (key, value) -> @_countdown = value 20 | 21 | @accessor 'timer', 22 | get: -> @_timer ? 0 23 | set: (key, value) -> @_timer = value 24 | 25 | @accessor 'input', 26 | get: -> @_input ? 0 27 | set: (key, value) -> @_input = value 28 | 29 | showTimer: -> 30 | $(@node).find('.icon').hide() 31 | $(@node).find('.timer').show() 32 | 33 | showIcon: -> 34 | $(@node).find('.timer').hide() 35 | $(@node).find('.icon').show() 36 | 37 | isModeSet: -> 38 | @get('mode') == @get('changemode') 39 | 40 | queryState: -> 41 | $.get '/homeassistant/inputselect', 42 | widgetId: @get('input'), 43 | (data) => 44 | json = JSON.parse data 45 | @set 'mode', json.value 46 | 47 | postModeState: -> 48 | oldMode = @get 'mode' 49 | @set 'mode', @get('changemode') 50 | $.post '/homeassistant/script', 51 | widgetId: @get('id'), 52 | (data) => 53 | json = JSON.parse data 54 | if json.error != 0 55 | @set 'mode', oldModeM 56 | 57 | ready: -> 58 | @showIcon() 59 | if @get('bgcolor') 60 | $(@node).css("background-color", @get('bgcolor')) 61 | 62 | 63 | onData: (data) -> 64 | 65 | changeModeDelayed: => 66 | if @get('timer') <= 0 67 | @showIcon() 68 | @postModeState() 69 | @_timeout = null 70 | else 71 | @showTimer() 72 | @set 'timer', @get('timer') - 1 73 | @_timeout = setTimeout(@changeModeDelayed, 1000) 74 | 75 | onClick: (event) -> 76 | if not @_timeout and not @isModeSet() 77 | @set 'timer', @get('countdown') 78 | @changeModeDelayed() 79 | -------------------------------------------------------------------------------- /widgets/hamode/hamode.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |
5 |

6 |
7 |
8 |

9 |
10 |
11 | -------------------------------------------------------------------------------- /widgets/hamode/hamode.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Widget-stmode styles 3 | // ---------------------------------------------------------------------------- 4 | .widget-hamode { 5 | 6 | .container { 7 | position: relative; 8 | height: 75px; 9 | } 10 | 11 | .icon, .timer { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | .timer { 20 | z-index: 1; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /widgets/hamotion/hamotion.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hamotion extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? "Unknown" 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'icon', 11 | get: -> if @get('state') == 'on' then 'exchange' else 'reorder' 12 | set: Batman.Property.defaultAccessor.set 13 | 14 | @accessor 'icon-style', -> 15 | if @get('state') == 'on' then 'icon-active' else 'icon-inactive' 16 | 17 | queryState: -> 18 | $.get '/homeassistant/binarysensor', 19 | widgetId: @get('id'), 20 | (data) => 21 | json = JSON.parse data 22 | @set 'state', json.state 23 | 24 | ready: -> 25 | if @get('bgcolor') 26 | $(@node).css("background-color", @get('bgcolor')) 27 | 28 | onData: (data) -> 29 | -------------------------------------------------------------------------------- /widgets/hamotion/hamotion.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | -------------------------------------------------------------------------------- /widgets/hamotion/hamotion.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Widget-stmotion styles 3 | // ---------------------------------------------------------------------------- 4 | .widget-hamotion { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /widgets/hascene/hascene.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hascene extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | 5 | state = 'off' 6 | 7 | @accessor 'icon', 8 | get: -> @['icon'] ? 'square' 9 | set: Batman.Property.defaultAccessor.set 10 | 11 | @accessor 'icon-style', 12 | get: -> if state == 'on' then 'icon-active' else 'icon-inactive' 13 | set: Batman.Property.defaultAccessor.set 14 | 15 | ready: -> 16 | if @get('bgcolor') 17 | $(@node).css("background-color", @get('bgcolor')) 18 | 19 | turnOff: => 20 | state = 'off' 21 | @set 'icon-style', 'icon-inactive' 22 | 23 | postScene: -> 24 | $.post '/homeassistant/scene', 25 | widgetId: @get('id'), 26 | 27 | onClick: (event) -> 28 | @postScene() 29 | state = 'on' 30 | @set 'icon-style', 'icon-active' 31 | @_timeout = setTimeout(@turnOff, @['ontime'] ? 1000) 32 | -------------------------------------------------------------------------------- /widgets/hascene/hascene.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |
5 |

6 |
7 |
8 | -------------------------------------------------------------------------------- /widgets/hascene/hascene.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-stmode styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hascene { 7 | 8 | .container { 9 | position: relative; 10 | height: 75px; 11 | } 12 | 13 | .icon { 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | width: 100%; 18 | height: 100%; 19 | } 20 | 21 | .icon-inactive { 22 | color: $color-inactive; 23 | } 24 | 25 | .icon-active { 26 | color: $color-active; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /widgets/hascript/hascript.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hascript extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | 5 | state = 'off' 6 | 7 | @accessor 'icon', 8 | get: -> @['icon'] ? 'tag' 9 | set: Batman.Property.defaultAccessor.set 10 | 11 | @accessor 'icon-style', 12 | get: -> if state == 'on' then 'icon-active' else 'icon-inactive' 13 | set: Batman.Property.defaultAccessor.set 14 | 15 | ready: -> 16 | if @get('bgcolor') 17 | $(@node).css("background-color", @get('bgcolor')) 18 | 19 | turnOff: => 20 | state = 'off' 21 | @set 'icon-style', 'icon-inactive' 22 | 23 | postScene: -> 24 | $.post '/homeassistant/script', 25 | widgetId: @get('id'), 26 | 27 | onClick: (event) -> 28 | @postScene() 29 | state = 'on' 30 | @set 'icon-style', 'icon-active' 31 | @_timeout = setTimeout(@turnOff, @['ontime'] ? 1000) 32 | -------------------------------------------------------------------------------- /widgets/hascript/hascript.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |
5 |

6 |
7 |
8 | -------------------------------------------------------------------------------- /widgets/hascript/hascript.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Widget-stmode styles 3 | // ---------------------------------------------------------------------------- 4 | .widget-hascript { 5 | 6 | .container { 7 | position: relative; 8 | height: 75px; 9 | } 10 | 11 | .icon { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /widgets/haselectdimmer/haselectdimmer.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Haselectdimmer extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | 7 | @accessor 'state', 8 | get: -> @_state ? 'off' 9 | set: (key, value) -> @_state = value 10 | 11 | @accessor 'level', 12 | get: -> if @get('state') != 'off' then @_level else 'Off' 13 | set: (key, value) -> @_level = value 14 | 15 | @accessor 'levellabel', 16 | get: -> if @get('state') != 'off' then @get('level') + '%' else 'Off' 17 | set: (key, value) -> @_level = value 18 | 19 | @accessor 'opacitylevel', 20 | get: -> if @get('state') == 'off' then '100 ; color:Black' else @get('level') / 50 21 | set: (key, value) -> @_opacitylevel = value 22 | 23 | postState: -> 24 | path = '/homeassistant/selectdimmer' 25 | $.post path, 26 | widgetId: @get('id'), 27 | 28 | queryState: -> 29 | path = '/dimmer/' 30 | $.get path, 31 | deviceId: @get('id') 32 | (data) => 33 | json = JSON.parse data 34 | @set 'state', json.state 35 | @set 'level', json.level 36 | 37 | ready: -> 38 | 39 | onData: (data) -> 40 | @queryState() 41 | 42 | onClick: (node, event) -> 43 | @postState() 44 | Dashing.cycleDashboardsNow( 45 | boardnumber: @get('page'), 46 | stagger: @get('stagger'), 47 | fastTransition: @get('fasttransition'), 48 | transitiontype: @get('transitiontype')) 49 | -------------------------------------------------------------------------------- /widgets/haselectdimmer/haselectdimmer.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 |

6 | -------------------------------------------------------------------------------- /widgets/haselectdimmer/haselectdimmer.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Widget-select-color styles 3 | // ---------------------------------------------------------------------------- 4 | .widget-select-dimmer { 5 | 6 | .title { 7 | color: rgba(255, 255, 255, 0.7); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /widgets/hasensor/hasensor.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hasensor extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'value', 7 | get: -> if @_value then (if isNaN(Math.round(@_value)) then @_value else Math.round(@_value) ) else "??" 8 | set: (key, value) -> @_value = value 9 | 10 | queryState: -> 11 | $.get '/homeassistant/sensor', 12 | widgetId: @get('id'), 13 | deviceId: @get('device') 14 | (data) => 15 | json = JSON.parse data 16 | @set 'value', json.value 17 | 18 | ready: -> 19 | if @get('bgcolor') 20 | $(@node).css("background-color", @get('bgcolor')) 21 | 22 | onData: (data) -> 23 | -------------------------------------------------------------------------------- /widgets/hasensor/hasensor.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | -------------------------------------------------------------------------------- /widgets/hasensor/hasensor.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget-hasensor styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-hasensor { 7 | 8 | .value { 9 | color: $blue; 10 | display: inline-block; 11 | vertical-align: middle; 12 | } 13 | 14 | .unit { 15 | color: $blue; 16 | font-size: 225%; 17 | font-weight: 400; 18 | display: inline-block; 19 | vertical-align: top; 20 | margin-left: 5px; 21 | margin-top: 5px; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /widgets/hasetlevel/hasetlevel.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hasetlevel extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | 5 | 6 | @accessor 'level', 7 | get: -> @_level ? "off" 8 | set: (key, value) -> @_level = value 9 | 10 | @accessor 'levellabel', 11 | get: -> if @get('level') != 'off' then @get('level') + '%' else 'Off' 12 | set: (key, value) -> @_level = value 13 | 14 | @accessor 'opacitylevel', 15 | get: -> if @get('level') == 'off' then '100 ; color:Black' else @get('level') / 100 16 | set: (key, value) -> @_opacitylevel = value 17 | 18 | postState: -> 19 | path = '/homeassistant/setdimmer' 20 | $.post path, 21 | command: @get('level'), 22 | 23 | 24 | ready: -> 25 | 26 | onData: (data) -> 27 | 28 | onClick: (node, event) -> 29 | @postState() 30 | Dashing.cycleDashboardsNow( 31 | boardnumber: @get('page'), 32 | stagger: @get('stagger'), 33 | fastTransition: @get('fasttransition'), 34 | transitiontype: @get('transitiontype')) 35 | -------------------------------------------------------------------------------- /widgets/hasetlevel/hasetlevel.html: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 | -------------------------------------------------------------------------------- /widgets/hasetlevel/hasetlevel.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Widget-select-color styles 3 | // ---------------------------------------------------------------------------- 4 | .widget-set-level { 5 | 6 | .title { 7 | color: rgba(255, 255, 255, 0.7); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /widgets/haswitch/haswitch.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Haswitch extends Dashing.ClickableWidget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'state', 7 | get: -> @_state ? 'off' 8 | set: (key, value) -> @_state = value 9 | 10 | @accessor 'icon', 11 | get: -> if @['icon'] then @['icon'] else 12 | if @get('state') == 'on' then @get('iconon') else @get('iconoff') 13 | set: Batman.Property.defaultAccessor.set 14 | 15 | @accessor 'iconon', 16 | get: -> @['iconon'] ? 'circle' 17 | set: Batman.Property.defaultAccessor.set 18 | 19 | @accessor 'iconoff', 20 | get: -> @['iconoff'] ? 'circle-thin' 21 | set: Batman.Property.defaultAccessor.set 22 | 23 | @accessor 'icon-style', -> 24 | if @get('state') == 'on' then 'switch-icon-on' else 'switch-icon-off' 25 | 26 | toggleState: -> 27 | newState = if @get('state') == 'on' then 'off' else 'on' 28 | @set 'state', newState 29 | return newState 30 | 31 | queryState: -> 32 | $.get '/homeassistant/switch', 33 | widgetId: @get('id'), 34 | (data) => 35 | json = JSON.parse data 36 | @set 'state', json.state 37 | 38 | postState: -> 39 | newState = @toggleState() 40 | $.post '/homeassistant/switch', 41 | widgetId: @get('id'), 42 | command: newState, 43 | (data) => 44 | json = JSON.parse data 45 | if json.error != 0 46 | @toggleState() 47 | 48 | ready: -> 49 | if @get('bgcolor') 50 | $(@node).css("background-color", @get('bgcolor')) 51 | 52 | onClick: (event) -> 53 | @postState() 54 | -------------------------------------------------------------------------------- /widgets/haswitch/haswitch.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

-------------------------------------------------------------------------------- /widgets/haswitch/haswitch.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | // ---------------------------------------------------------------------------- 3 | // Widget styles 4 | // ---------------------------------------------------------------------------- 5 | .widget-haswitch { 6 | 7 | .switch-icon-off .switch-icon-on { 8 | font-size: 150%; 9 | } 10 | 11 | .switch-icon-off { 12 | color: $gray-light; 13 | } 14 | 15 | .switch-icon-on { 16 | color: $green; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /widgets/hatemp/hatemp.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Hatemp extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @queryState() 5 | 6 | @accessor 'value', 7 | get: -> if @_value then Math.round(@_value) else 0 8 | set: (key, value) -> @_value = value 9 | 10 | queryState: -> 11 | $.get '/homeassistant/temperature', 12 | widgetId: @get('id'), 13 | deviceId: @get('device') 14 | (data) => 15 | json = JSON.parse data 16 | @set 'value', json.value 17 | 18 | ready: -> 19 | 20 | onData: (data) -> 21 | -------------------------------------------------------------------------------- /widgets/hatemp/hatemp.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | -------------------------------------------------------------------------------- /widgets/hatemp/hatemp.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | // ---------------------------------------------------------------------------- 3 | // Widget-sttemp styles 4 | // ---------------------------------------------------------------------------- 5 | .widget-hatemp { 6 | 7 | .value { 8 | color: $blue; 9 | display: inline-block; 10 | vertical-align: middle; 11 | } 12 | 13 | .unit { 14 | color: $blue; 15 | font-size: 225%; 16 | font-weight: 400; 17 | display: inline-block; 18 | vertical-align: top; 19 | margin-left: 5px; 20 | margin-top: 5px; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /widgets/haweather/haweather.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Haweather extends Dashing.Widget 2 | constructor: -> 3 | super 4 | @_icons = 5 | rain: '', 6 | snow: '', 7 | sleet: '', 8 | wind: '', 9 | fog: '', 10 | cloudy: '', 11 | clear_day: '', 12 | clear_night: '', 13 | partly_cloudy_day: '', 14 | partly_cloudy_night: '' 15 | 16 | @accessor 'climacon', -> 17 | new Batman.TerminalAccessible (attr) => 18 | @_icons[attr] 19 | 20 | @accessor 'now_temp', 21 | get: -> if @_temp then Math.floor(@_temp) else 0 22 | set: (key, value) -> @_temp = value 23 | 24 | ready: -> 25 | if @get('bgcolor') 26 | $(@node).css("background-color", @get('bgcolor')) 27 | 28 | onData: (data) -> 29 | -------------------------------------------------------------------------------- /widgets/haweather/haweather.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 |
7 |

Humidity:

8 |

9 |
10 |

Apparent Temperature:

11 |

12 |
13 |

Rain:

14 |

15 |

/

16 |

17 |
18 |

Wind:

19 |

20 |

/

21 |

22 |
23 |

Pressure:

24 |

25 | -------------------------------------------------------------------------------- /widgets/haweather/haweather.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | // ---------------------------------------------------------------------------- 3 | // Widget-stmode styles 4 | // ---------------------------------------------------------------------------- 5 | .widget-haweather { 6 | 7 | color: $orange; 8 | 9 | h1 { 10 | margin-bottom: 0px; 11 | } 12 | 13 | [class^="primary"] { 14 | display: inline-block; 15 | vertical-align: middle; 16 | padding: 0; 17 | margin-left: 10px; 18 | margin-right: 10px; 19 | margin-top: 0px; 20 | margin-bottom: 0px; 21 | } 22 | 23 | [class^="secondary"] { 24 | display: inline-block; 25 | vertical-align: middle; 26 | padding: 0; 27 | margin-left: 0px; 28 | margin-right: 0px; 29 | margin-top: 0px; 30 | margin-bottom: 0px; 31 | } 32 | 33 | .primary-climacon { 34 | font-family: "Climacons-Font"; 35 | font-size: 70px; 36 | } 37 | 38 | .primary-info { 39 | font-size: 300%; 40 | font-weight: 400; 41 | } 42 | 43 | .primary-unit { 44 | font-size: 200%; 45 | font-weight: 400; 46 | margin-left: 0; 47 | margin-top: 20px; 48 | vertical-align: top; 49 | } 50 | 51 | .secondary-icon { 52 | font-family: "Climacons-Font"; 53 | font-size: 40px; 54 | margin-left: 10px; 55 | margin-right: 0px; 56 | } 57 | 58 | .secondary-climacon { 59 | font-family: "Climacons-Font"; 60 | font-size: 55px; 61 | margin-left: 10px; 62 | margin-right: 10px; 63 | } 64 | 65 | .secondary-info { 66 | font-size: 125%; 67 | font-weight: 200; 68 | } 69 | 70 | hr { 71 | width: 90%; 72 | border: 0; 73 | height: 1px; 74 | background: rgba(255, 255, 255, 0.25); 75 | margin-top: 15px; 76 | margin-bottom: 15px; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /widgets/iframe/iframe.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Iframe extends Dashing.Widget 2 | 3 | ready: -> 4 | $(@node).find(".iframe").attr('src', @get('src')) 5 | 6 | onData: (data) -> 7 | $(@node).find(".iframe").attr('src', data.src) -------------------------------------------------------------------------------- /widgets/iframe/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /widgets/iframe/iframe.scss: -------------------------------------------------------------------------------- 1 | .widget-iframe { 2 | padding: 3px 0px 0px 0px !important; 3 | 4 | iframe { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /widgets/image/image.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Image extends Dashing.Widget 2 | 3 | ready: -> 4 | # This is fired when the widget is done being rendered 5 | 6 | onData: (data) -> 7 | # Handle incoming data 8 | # You can access the html node of this widget with `@node` 9 | # Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in. 10 | -------------------------------------------------------------------------------- /widgets/image/image.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /widgets/image/image.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #4b4b4b; 5 | 6 | // ---------------------------------------------------------------------------- 7 | // Widget-image styles 8 | // ---------------------------------------------------------------------------- 9 | .widget-image { 10 | 11 | background-color: $background-color; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /widgets/list/list.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.List extends Dashing.Widget 2 | ready: -> 3 | if @get('unordered') 4 | $(@node).find('ol').remove() 5 | else 6 | $(@node).find('ul').remove() 7 | -------------------------------------------------------------------------------- /widgets/list/list.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |
    4 |
  1. 5 | 6 | 7 |
  2. 8 |
9 | 10 |
    11 |
  • 12 | 13 | 14 |
  • 15 |
16 | 17 |

18 |

19 | -------------------------------------------------------------------------------- /widgets/list/list.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #12b0c5; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $label-color: rgba(255, 255, 255, 0.7); 9 | $moreinfo-color: rgba(255, 255, 255, 0.7); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-list styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-list { 15 | 16 | background-color: $background-color; 17 | vertical-align: top; 18 | 19 | .title { 20 | color: $title-color; 21 | } 22 | 23 | ol, ul { 24 | margin: 0 15px; 25 | text-align: left; 26 | color: $label-color; 27 | } 28 | 29 | ol { 30 | list-style-position: inside; 31 | } 32 | 33 | li { 34 | margin-bottom: 5px; 35 | } 36 | 37 | .list-nostyle { 38 | list-style: none; 39 | } 40 | 41 | .label { 42 | color: $label-color; 43 | } 44 | 45 | .value { 46 | float: right; 47 | margin-left: 12px; 48 | font-weight: 600; 49 | color: $value-color; 50 | } 51 | 52 | .updated-at { 53 | color: rgba(0, 0, 0, 0.3); 54 | } 55 | 56 | .more-info { 57 | color: $moreinfo-color; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /widgets/meter/meter.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Meter extends Dashing.Widget 2 | 3 | @accessor 'value', Dashing.AnimatedValue 4 | 5 | constructor: -> 6 | super 7 | @observe 'value', (value) -> 8 | $(@node).find(".meter").val(value).trigger('change') 9 | 10 | ready: -> 11 | meter = $(@node).find(".meter") 12 | meter.attr("data-bgcolor", meter.css("background-color")) 13 | meter.attr("data-fgcolor", meter.css("color")) 14 | meter.knob() 15 | -------------------------------------------------------------------------------- /widgets/meter/meter.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /widgets/meter/meter.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #9c4274; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.3); 8 | 9 | $meter-background: darken($background-color, 15%); 10 | 11 | // ---------------------------------------------------------------------------- 12 | // Widget-meter styles 13 | // ---------------------------------------------------------------------------- 14 | .widget-meter { 15 | 16 | background-color: $background-color; 17 | 18 | input.meter { 19 | background-color: $meter-background; 20 | color: #fff; 21 | } 22 | 23 | .title { 24 | color: $title-color; 25 | } 26 | 27 | .more-info { 28 | color: $moreinfo-color; 29 | } 30 | 31 | .updated-at { 32 | color: rgba(0, 0, 0, 0.3); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /widgets/news/news.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.News extends Dashing.Widget 2 | 3 | ready: -> 4 | @currentIndex = 0 5 | @headlineElem = $(@node).find('.headline-container') 6 | @nextComment() 7 | @startCarousel() 8 | if @get('bgcolor') 9 | $(@node).css("background-color", @get('bgcolor')) 10 | 11 | onData: (data) -> 12 | @currentIndex = 0 13 | 14 | startCarousel: -> 15 | interval = $(@node).attr('data-interval') 16 | interval = "30" if not interval 17 | setInterval(@nextComment, parseInt( interval ) * 1000) 18 | 19 | nextComment: => 20 | headlines = @get('headlines') 21 | if headlines 22 | @headlineElem.fadeOut => 23 | @currentIndex = (@currentIndex + 1) % headlines.length 24 | @set 'current_headline', headlines[@currentIndex] 25 | @headlineElem.fadeIn() 26 | -------------------------------------------------------------------------------- /widgets/news/news.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

5 |
6 |
-------------------------------------------------------------------------------- /widgets/news/news.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $value-color: #fff; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7);; 7 | $moreinfo-color: rgba(255, 255, 255, 0.8);; 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-news styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-news { 13 | 14 | vertical-align: middle !important; 15 | 16 | .headline-container { 17 | display: none; 18 | height: 120px; 19 | } 20 | 21 | .title { 22 | font-size: 1.2em; 23 | } 24 | 25 | .description { 26 | font-size: 0.8em; 27 | color: $moreinfo-color; 28 | } 29 | 30 | .heading{ 31 | color: fff; 32 | font-size: 2em; 33 | vertical-align: top; 34 | margin-bottom: 10px; 35 | } 36 | 37 | .headline { 38 | font-size: 1em; 39 | vertical-align: top; 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /widgets/number/number.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Number extends Dashing.Widget 2 | @accessor 'current', Dashing.AnimatedValue 3 | 4 | @accessor 'difference', -> 5 | if @get('last') 6 | last = parseInt(@get('last')) 7 | current = parseInt(@get('current')) 8 | if last != 0 9 | diff = Math.abs(Math.round((current - last) / last * 100)) 10 | "#{diff}%" 11 | else 12 | "" 13 | 14 | @accessor 'arrow', -> 15 | if @get('last') 16 | if parseInt(@get('current')) > parseInt(@get('last')) then 'icon-arrow-up' else 'icon-arrow-down' 17 | 18 | onData: (data) -> 19 | if data.status 20 | # clear existing "status-*" classes 21 | $(@get('node')).attr 'class', (i,c) -> 22 | c.replace /\bstatus-\S+/g, '' 23 | # add new class 24 | $(@get('node')).addClass "status-#{data.status}" 25 | 26 | onClick: (node, event) -> 27 | $.post '/smartthings/dispatch', 28 | widgetId: @get('id'), 29 | deviceId: 'Console Lamp', 30 | command: 'toggle' 31 | (data) -> 32 | alert data 33 | -------------------------------------------------------------------------------- /widgets/number/number.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 |

10 | 11 |

12 | -------------------------------------------------------------------------------- /widgets/number/number.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #47bbb3; 5 | $value-color: #fff; 6 | 7 | $title-color: rgba(255, 255, 255, 0.7); 8 | $moreinfo-color: rgba(255, 255, 255, 0.7); 9 | 10 | // ---------------------------------------------------------------------------- 11 | // Widget-number styles 12 | // ---------------------------------------------------------------------------- 13 | .widget-number { 14 | 15 | background-color: $background-color; 16 | 17 | .title { 18 | color: $title-color; 19 | } 20 | 21 | .value { 22 | color: $value-color; 23 | } 24 | 25 | .change-rate { 26 | font-weight: 500; 27 | font-size: 150%; 28 | color: $value-color; 29 | } 30 | 31 | .more-info { 32 | color: $moreinfo-color; 33 | } 34 | 35 | .updated-at { 36 | color: rgba(0, 0, 0, 0.3); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /widgets/reload/reload.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Reload extends Dashing.ClickableWidget 2 | 3 | ready: -> 4 | if @get('bgcolor') 5 | $(@node).css("background-color", @get('bgcolor')) 6 | 7 | onData: (data) -> 8 | 9 | onClick: (event) -> 10 | Dashing.fire 'reload' 11 | -------------------------------------------------------------------------------- /widgets/reload/reload.html: -------------------------------------------------------------------------------- 1 |

Reload

2 | 3 |

-------------------------------------------------------------------------------- /widgets/reload/reload.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | // ---------------------------------------------------------------------------- 4 | // Widget styles 5 | // ---------------------------------------------------------------------------- 6 | .widget-reload { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /widgets/text/text.coffee: -------------------------------------------------------------------------------- 1 | class Dashing.Text extends Dashing.Widget 2 | -------------------------------------------------------------------------------- /widgets/text/text.html: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | -------------------------------------------------------------------------------- /widgets/text/text.scss: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Sass declarations 3 | // ---------------------------------------------------------------------------- 4 | $background-color: #ec663c; 5 | 6 | $title-color: rgba(255, 255, 255, 0.7); 7 | $moreinfo-color: rgba(255, 255, 255, 0.7); 8 | 9 | // ---------------------------------------------------------------------------- 10 | // Widget-text styles 11 | // ---------------------------------------------------------------------------- 12 | .widget-text { 13 | 14 | background-color: $background-color; 15 | 16 | .title { 17 | color: $title-color; 18 | } 19 | 20 | .more-info { 21 | color: $moreinfo-color; 22 | } 23 | 24 | .updated-at { 25 | color: rgba(255, 255, 255, 0.7); 26 | } 27 | 28 | 29 | &.large h3 { 30 | font-size: 325%; 31 | } 32 | } 33 | --------------------------------------------------------------------------------