├── .github └── FUNDING.yml ├── .gitignore ├── .vscode └── extensions.json ├── LICENSE.md ├── README.md ├── custom_components └── dwains_dashboard │ ├── __init__.py │ ├── config_flow.py │ ├── const.py │ ├── js │ ├── .gitignore │ ├── dwains-dashboard.js │ ├── dwains-dashboard.js.LICENSE.txt │ ├── dwains-dashboard.js.map │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── translations.js │ ├── webpack.config.js │ └── webpack.config.js.gz │ ├── load_dashboard.py │ ├── load_plugins.py │ ├── lovelace │ ├── ui-lovelace.yaml │ └── views │ │ ├── 01.homepage.yaml │ │ ├── 02.devices.yaml │ │ ├── 04.more_page_view.yaml │ │ └── 05.more_pages.yaml │ ├── manifest.json │ ├── notifications.py │ ├── process_yaml.py │ ├── sensor.py │ ├── services.yaml │ └── translations │ ├── bg.json │ ├── en.json │ ├── pl.json │ └── zh.json └── hacs.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [dwainscheeren] 2 | custom: ["https://www.paypal.me/dwainscheeren", "https://www.buymeacoffee.com/FAkYvrx"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dwains-theme/configs 2 | .HA_VERSION 3 | .storage/ 4 | automations.yaml 5 | configuration.yaml 6 | groups.yaml 7 | home-assistant.log 8 | home-assistant_v2.db 9 | scenes.yaml 10 | scripts.yaml 11 | secrets.yaml 12 | dwains-theme/.vscode/* 13 | dwains-theme/resources/custom_resources.yaml 14 | dwains-theme/addons/house_information/*/ 15 | !dwains_theme/addons/house_information/hello-house-information/ 16 | custom_components/*/ 17 | !custom_components/dwains_dashboard 18 | custom_components/dwains_dashboard/__pycache__/ 19 | custom_components/dwains_dashboard/.installed 20 | www/ 21 | packages/ 22 | packages/dwains-theme/ 23 | customize.yaml 24 | google_calendars.yaml 25 | zigbee.db 26 | known_devices.yaml 27 | home-assistant_v2.db-shm 28 | home-assistant_v2.db-wal 29 | .vscode/settings.json 30 | neighbours/ 31 | tts/ 32 | .google.token 33 | dwainconfig/ 34 | dwains-dashboard/ 35 | dwains-dashboardd/ 36 | /themes/ 37 | test/ 38 | image/ 39 | ._.DS_Store 40 | ._configuration.yaml 41 | .DS_Store 42 | blueprints/ 43 | ._* 44 | .fuse_hidden002e3c9700002485 45 | home-assistant.log.1 46 | home-assistant.log.fault 47 | minimalist-templates/ 48 | lovelace-minimalist.yaml 49 | 50 | custom_components/dwains_dashboard/cards/dwains-homepage-card/src.zip 51 | test.yaml 52 | H8HF46~F 53 | HQVM5K~K 54 | HVFWCC~H 55 | changelog.MD 56 | dwains-dashboard copy/ 57 | src-backup-3.6/ 58 | custom_components/dwains_dashboard/js/dwains-dashboard.js.map 59 | custom_components/dwains_dashboard/js/dwains-dashboard.js.map 60 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-python.python" 4 | ] 5 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Attribution-NonCommercial-NoDerivatives 4.0 International 2 | 3 | > *Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.* 4 | > 5 | > ### Using Creative Commons Public Licenses 6 | > 7 | > Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. 8 | > 9 | > * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). 10 | > 11 | > * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). 12 | 13 | ## Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License 14 | 15 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 16 | 17 | ### Section 1 – Definitions. 18 | 19 | a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 20 | 21 | b. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 22 | 23 | e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 24 | 25 | f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 26 | 27 | h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 28 | 29 | i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 30 | 31 | h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. 32 | 33 | i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. 34 | 35 | j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 36 | 37 | k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 38 | 39 | l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 40 | 41 | ### Section 2 – Scope. 42 | 43 | a. ___License grant.___ 44 | 45 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 46 | 47 | A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and 48 | 49 | B. produce and reproduce, but not Share, Adapted Material for NonCommercial purposes only. 50 | 51 | 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 52 | 53 | 3. __Term.__ The term of this Public License is specified in Section 6(a). 54 | 55 | 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 56 | 57 | 5. __Downstream recipients.__ 58 | 59 | A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 60 | 61 | B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 62 | 63 | 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 64 | 65 | b. ___Other rights.___ 66 | 67 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 68 | 69 | 2. Patent and trademark rights are not licensed under this Public License. 70 | 71 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. 72 | 73 | ### Section 3 – License Conditions. 74 | 75 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 76 | 77 | a. ___Attribution.___ 78 | 79 | 1. If You Share the Licensed Material, You must: 80 | 81 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 82 | 83 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 84 | 85 | ii. a copyright notice; 86 | 87 | iii. a notice that refers to this Public License; 88 | 89 | iv. a notice that refers to the disclaimer of warranties; 90 | 91 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 92 | 93 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 94 | 95 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 96 | 97 | For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material. 98 | 99 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 100 | 101 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 102 | 103 | ### Section 4 – Sui Generis Database Rights. 104 | 105 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 106 | 107 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only and provided You do not Share Adapted Material; 108 | 109 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 110 | 111 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 112 | 113 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 114 | 115 | ### Section 5 – Disclaimer of Warranties and Limitation of Liability. 116 | 117 | a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ 118 | 119 | b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ 120 | 121 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 122 | 123 | ### Section 6 – Term and Termination. 124 | 125 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 126 | 127 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 128 | 129 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 130 | 131 | 2. upon express reinstatement by the Licensor. 132 | 133 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 134 | 135 | c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 136 | 137 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 138 | 139 | ### Section 7 – Other Terms and Conditions. 140 | 141 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 142 | 143 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 144 | 145 | ### Section 8 – Interpretation. 146 | 147 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 148 | 149 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 150 | 151 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 152 | 153 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 154 | 155 | > Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 156 | > 157 | > Creative Commons may be contacted at [creativecommons.org](http://creativecommons.org). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dwains-lovelace-dashboard 2 | 3 | ## Important Announcement 4 | 5 | As of now, I have officially stopped active development on Dwains Dashboard. While I will continue to merge community-submitted pull requests (PRs), my focus has shifted entirely to my new project: [SmartHomeShop.io](https://smarthomeshop.io/en), where I develop innovative hardware products for smart homes—built around Home Assistant and ESPHome. 6 | 7 | Dwains Dashboard now works again with Home Assistant 2024.4, thanks to the amazing efforts of the community. However, I no longer have the time to maintain this project at the same level as before. I encourage users to test and verify PRs to ensure compatibility with future Home Assistant updates. Once verified, I will gladly merge them and publish a new release. 8 | 9 | I also want to take a moment to address a recent misunderstanding. Dwains Dashboard was a free, open-source passion project that I worked on for years in my spare time, alongside my regular job—completely without commercial intent. SmartHomeShop.io, on the other hand, is a professional and sustainable venture that allows me to continue contributing to the Home Assistant ecosystem in a different way: through reliable hardware and supported solutions that are actually built to last. 10 | 11 | To be fully transparent: I’ve sometimes felt overlooked or treated as a small player by parts of the Home Assistant team, despite everything Dwains Dashboard has meant to the community over the past few years. That’s okay—I'm proud of what we've built together. And more than ever, I feel motivated to keep innovating and supporting the community, now through a different medium. 12 | 13 | That’s why I’d like to ask for a small favor: if you’ve appreciated my work over the years, it would mean the world to me if you’d share [SmartHomeShop.io](https://smarthomeshop.io/en) with others. Whether it’s through social media, word of mouth, or simply mentioning it in a Discord or forum post—it all helps an independent creator like me immensely. 14 | 15 | Thank you to everyone who has supported Dwains Dashboard over the years. It’s been an incredible journey, and I’m genuinely proud of what we’ve achieved together. Moving forward, I’m excited to keep contributing—this time through high-quality hardware and solutions tailored specifically for Home Assistant users. 16 | 17 | Thanks again for your understanding and continued support 🙏 18 | — Dwain 19 | 20 | 21 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) 22 | 23 | 24 | 25 | 26 | 27 | ![GitHub stars](https://img.shields.io/github/stars/dwainscheeren/dwains-lovelace-dashboard?style=social) 28 | ![GitHub forks](https://img.shields.io/github/forks/dwainscheeren/dwains-lovelace-dashboard?style=social) 29 | ![GitHub watchers](https://img.shields.io/github/watchers/dwainscheeren/dwains-lovelace-dashboard?style=social) 30 | ![GitHub followers](https://img.shields.io/github/followers/dwainscheeren?style=social) 31 | 32 | 33 | 34 | Buy Me A Coffee 35 | 36 | Donate with PayPal button 37 | 38 | [**How to install Dwains Dashboard**](https://dwainscheeren.github.io/dwains-lovelace-dashboard/v3/information/installation.html#installation) 39 | 40 | ![github-1 copy](https://user-images.githubusercontent.com/3868853/164969724-bda3d9ed-f86e-4f69-9583-2302ffc28bd9.jpg) 41 | ![github-2](https://user-images.githubusercontent.com/3868853/164969716-37242ed4-c2f1-4f59-9fc9-a277138033ff.jpg) 42 | ![github-3](https://user-images.githubusercontent.com/3868853/164969718-4353c600-5dff-4626-af3a-1a3f1b540332.jpg) 43 | ![github-4](https://user-images.githubusercontent.com/3868853/164969719-e40b1119-bf76-47a0-ae83-8af127fdb12f.jpg) 44 | 45 | 46 | 47 | # Dwains Lovelace Dashboard 48 | 49 | Hello, I am Dwain. I've been using Home Assistant for over two years now. At the end of summer 2019 I thought, "Why hasn't anybody made a dashboard yet that builds itself automatically by providing only minimum configuration info?" 50 | 51 | I own a web development company, and I want to give something back to the Home Assistant community. So I decided to build this dashboard and release it to the public. The result: Dwains Lovelace Dashboard. 52 | 53 | **Install** 54 | 55 | Want to know how to install Dwains Dashboard? Read the installation instructions here: [https://dwainscheeren.github.io/dwains-lovelace-dashboard/v3/information/installation.html](https://dwainscheeren.github.io/dwains-lovelace-dashboard/v3/information/installation.html#installation). It are only 3 steps! :) 56 | Please note if you had previously Dwains Dashboard v1 or v2 installed you need to [follow the upgrade guide explained here](https://dwainscheeren.github.io/dwains-lovelace-dashboard/v3/information/migrate-v2-to-v3.html#migrate-from-existing-dwains-dashboard-v2-installation-to-v3). 57 | 58 | **Want to request a new feature request?** 59 | 60 | [Open an issue on GitHub](https://github.com/dwainscheeren/dwains-lovelace-dashboard/issues/new) with the title starting with  `FR`  or  `Feature Request`. 61 | 62 | **Support** 63 | 64 | You can use the [thread on the Home Assistant community](https://community.home-assistant.io/t/dwains-theme-an-auto-generating-lovelace-ui-theme/168593) to ask any questions you have. 65 | 66 | Or join me on Dwains Dashboard Discord [Discord Server Home Assistant Addicts](https://discord.gg/7yt64uX). 67 | 68 | **YouTube Channel** 69 | 70 | If you like my content, then please do subscribe to my [Home Assistant YouTube channel](https://www.youtube.com/channel/UCb2GBaLC4d0rVn9pZbYbQ9A) I will start posting some more videos there soon. 71 | 72 | **Like what you see?** 73 | 74 | If you appreciate what I have developed, then please consider buying me a coffe or beer: [Buy me a coffee/beer](https://www.buymeacoffee.com/FAkYvrx) or [donate to my PayPal account](https://www.paypal.me/dwainscheeren). 75 | You can also reply to [this HA thread](https://community.home-assistant.io/t/dwains-theme-an-auto-generating-lovelace-ui-theme/168593) and let everyone know how happy you are with this dashboard! 76 | 77 | Greetings, 78 | 79 | Dwain 80 | 81 | **EXTRA LICENSE INFORMATION** 82 | You are not allowed without my permission to resell, re-distribute or sell my dashboard! 83 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import yaml 3 | import json 4 | import os 5 | import shutil 6 | 7 | from concurrent.futures import ThreadPoolExecutor 8 | 9 | from .load_plugins import load_plugins 10 | from .load_dashboard import load_dashboard 11 | from .const import DOMAIN, VERSION 12 | from .process_yaml import process_yaml, reload_configuration 13 | from .notifications import notifications 14 | from datetime import datetime 15 | 16 | import voluptuous as vol 17 | from homeassistant.core import HomeAssistant, callback 18 | from homeassistant.config import ConfigType 19 | from homeassistant.components import frontend, websocket_api 20 | from homeassistant.helpers import entity_registry as er 21 | from homeassistant.util import slugify 22 | from homeassistant.const import Platform 23 | 24 | from collections import OrderedDict 25 | from typing import Any, Mapping, MutableMapping, Optional 26 | 27 | from homeassistant.helpers import discovery 28 | 29 | from yaml.representer import Representer 30 | import collections 31 | import asyncio 32 | import aiofiles 33 | 34 | _LOGGER = logging.getLogger(__name__) 35 | 36 | areas = OrderedDict() 37 | entities = OrderedDict() 38 | devices = OrderedDict() 39 | homepage_header = OrderedDict() 40 | 41 | async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 42 | #_LOGGER.warning("async_setup") 43 | 44 | #_LOGGER.warning(config) 45 | #_LOGGER.warning(hass.data[DOMAIN]) 46 | 47 | # if not config.get(DOMAIN): 48 | # _LOGGER.warning("no config") 49 | 50 | hass.data[DOMAIN] = { 51 | "notifications": {}, 52 | "commands": {}, 53 | 'latest_version': "" 54 | } 55 | 56 | websocket_api.async_register_command(hass, websocket_get_configuration) 57 | websocket_api.async_register_command(hass, websocket_get_blueprints) 58 | 59 | websocket_api.async_register_command(hass, ws_handle_install_blueprint) 60 | websocket_api.async_register_command(hass, ws_handle_delete_blueprint) 61 | 62 | websocket_api.async_register_command(hass, ws_handle_add_card) 63 | websocket_api.async_register_command(hass, ws_handle_remove_card) 64 | 65 | websocket_api.async_register_command(hass, ws_handle_edit_entity) 66 | websocket_api.async_register_command(hass, ws_handle_edit_entity_card) 67 | websocket_api.async_register_command(hass, ws_handle_edit_entity_popup) 68 | websocket_api.async_register_command(hass, ws_handle_edit_entity_favorite) 69 | websocket_api.async_register_command(hass, ws_handle_edit_entity_bool_value) 70 | websocket_api.async_register_command(hass, ws_handle_edit_entities_bool_value) 71 | websocket_api.async_register_command(hass, ws_handle_edit_device_button) 72 | websocket_api.async_register_command(hass, ws_handle_edit_device_card) 73 | websocket_api.async_register_command(hass, ws_handle_edit_device_popup) 74 | websocket_api.async_register_command(hass, ws_handle_edit_device_bool_value) 75 | websocket_api.async_register_command(hass, ws_handle_remove_device_card) 76 | websocket_api.async_register_command(hass, ws_handle_remove_device_popup) 77 | websocket_api.async_register_command(hass, ws_handle_remove_entity_card) 78 | websocket_api.async_register_command(hass, ws_handle_remove_entity_popup) 79 | 80 | websocket_api.async_register_command(hass, ws_handle_edit_area_button) 81 | websocket_api.async_register_command(hass, ws_handle_edit_area_bool_value) 82 | 83 | websocket_api.async_register_command(hass, ws_handle_edit_homepage_header) 84 | 85 | websocket_api.async_register_command(hass, ws_handle_edit_more_page_button) 86 | websocket_api.async_register_command(hass, ws_handle_edit_more_page) 87 | websocket_api.async_register_command(hass, ws_handle_remove_more_page) 88 | websocket_api.async_register_command(hass, ws_handle_add_more_page_to_navbar) 89 | 90 | websocket_api.async_register_command(hass, ws_handle_sort_area_button) 91 | websocket_api.async_register_command(hass, ws_handle_sort_device_button) 92 | websocket_api.async_register_command(hass, ws_handle_sort_entity) 93 | websocket_api.async_register_command(hass, ws_handle_sort_more_page) 94 | 95 | await load_plugins(hass, DOMAIN) 96 | 97 | notifications(hass, DOMAIN) 98 | 99 | return True 100 | 101 | yaml.add_representer(collections.OrderedDict, Representer.represent_dict) 102 | 103 | @websocket_api.async_response 104 | @websocket_api.websocket_command({vol.Required("type"): "dwains_dashboard/configuration/get"}) 105 | async def websocket_get_configuration( 106 | hass: HomeAssistant, 107 | connection: websocket_api.ActiveConnection, 108 | msg: Mapping[str, Any], 109 | ) -> None: 110 | """Return a list of configuration.""" 111 | 112 | # Initialize all needed variables 113 | #areas = OrderedDict() 114 | #entities = OrderedDict() 115 | #devices = OrderedDict() 116 | #homepage_header = OrderedDict() 117 | global areas 118 | global entities 119 | global devices 120 | global homepage_header 121 | 122 | # These need to be loaded here so any changes are reflected immediately. 123 | areas = ( 124 | await hass.async_add_executor_job(os.path.exists, hass.config.path("dwains-dashboard/configs/areas.yaml")) 125 | ) 126 | 127 | if areas: 128 | areas = await hass.async_add_executor_job( 129 | lambda: yaml.safe_load(open(hass.config.path("dwains-dashboard/configs/areas.yaml"), "r")) 130 | ) 131 | else: 132 | areas = OrderedDict() 133 | 134 | entities = ( 135 | await hass.async_add_executor_job(os.path.exists, hass.config.path("dwains-dashboard/configs/entities.yaml")) 136 | ) 137 | 138 | if entities: 139 | entities = await hass.async_add_executor_job( 140 | lambda: yaml.safe_load(open(hass.config.path("dwains-dashboard/configs/entities.yaml"), "r")) 141 | ) 142 | else: 143 | entities = OrderedDict() 144 | 145 | 146 | devices = ( 147 | await hass.async_add_executor_job(os.path.exists, hass.config.path("dwains-dashboard/configs/devices.yaml")) 148 | ) 149 | 150 | if devices: 151 | devices = await hass.async_add_executor_job( 152 | lambda: yaml.safe_load(open(hass.config.path("dwains-dashboard/configs/devices.yaml"), "r")) 153 | ) 154 | else: 155 | devices = OrderedDict() 156 | 157 | homepage_header = ( 158 | await hass.async_add_executor_job(os.path.exists, hass.config.path("dwains-dashboard/configs/settings.yaml")) 159 | ) 160 | 161 | if homepage_header: 162 | homepage_header = await hass.async_add_executor_job( 163 | lambda: yaml.safe_load(open(hass.config.path("dwains-dashboard/configs/settings.yaml"), "r")) 164 | ) 165 | else: 166 | homepage_header = OrderedDict() 167 | 168 | area_cards = {} 169 | if os.path.isdir(hass.config.path("dwains-dashboard/configs/cards/areas")): 170 | #for subdir in await listdir_async(hass.config.path("dwains-dashboard/configs/cards/areas")): 171 | subdirs = await hass.async_add_executor_job(os.listdir, hass.config.path("dwains-dashboard/configs/cards/areas")) 172 | for subdir in subdirs: 173 | area_cards[subdir] = {} 174 | #for fname in sorted(await listdir_async(hass.config.path(f"dwains-dashboard/configs/cards/areas/{subdir}"))): 175 | fnames = sorted(await hass.async_add_executor_job(os.listdir, hass.config.path(f"dwains-dashboard/configs/cards/areas/{subdir}"))) 176 | for fname in fnames: 177 | if fname.endswith('.yaml'): 178 | #_LOGGER.warning(f"websocket_get_configuration() area_cards: {fname}") 179 | data = await hass.async_add_executor_job(open, hass.config.path(f"dwains-dashboard/configs/cards/areas/{subdir}/{fname}"), "r") 180 | with data as f: 181 | filecontent = yaml.safe_load(f) 182 | area_cards[subdir].update({fname: filecontent}) 183 | 184 | device_cards = {} 185 | if os.path.isdir(hass.config.path("dwains-dashboard/configs/cards/devices")): 186 | #for subdir in await listdir_async(hass.config.path("dwains-dashboard/configs/cards/devices")): 187 | subdirs = await hass.async_add_executor_job(os.listdir, hass.config.path("dwains-dashboard/configs/cards/devices")) 188 | for subdir in subdirs: 189 | device_cards[subdir] = {} 190 | #for fname in sorted(await listdir_async(hass.config.path(f"dwains-dashboard/configs/cards/devices/{subdir}"))): 191 | fnames = sorted(await hass.async_add_executor_job(os.listdir, hass.config.path(f"dwains-dashboard/configs/cards/devices/{subdir}"))) 192 | for fname in fnames: 193 | if fname.endswith('.yaml'): 194 | #_LOGGER.warning(f"websocket_get_configuration() device_cards: {fname}") 195 | #file_path = hass.config.path(f"dwains-dashboard/configs/cards/devices/{subdir}/{fname}") 196 | #filecontent = await read_yaml_file(file_path) 197 | data = await hass.async_add_executor_job(open, hass.config.path(f"dwains-dashboard/configs/cards/devices/{subdir}/{fname}"), "r") 198 | with data as f: 199 | filecontent = yaml.safe_load(f) 200 | device_cards[subdir].update({fname: filecontent}) 201 | 202 | 203 | entity_cards = {} 204 | if os.path.isdir(hass.config.path("dwains-dashboard/configs/cards/entities")): 205 | #for fname in await listdir_async(hass.config.path("dwains-dashboard/configs/cards/entities")): 206 | fnames = await hass.async_add_executor_job(os.listdir, hass.config.path("dwains-dashboard/configs/cards/entities")) 207 | for fname in fnames: 208 | if fname.endswith('.yaml'): 209 | #_LOGGER.warning(f"websocket_get_configuration() entity_cards: {fname}") 210 | #file_path = hass.config.path(f"dwains-dashboard/configs/cards/entities/{fname}") 211 | #filecontent = await read_yaml_file(file_path) 212 | data = await hass.async_add_executor_job(open, hass.config.path(f"dwains-dashboard/configs/cards/entities/{fname}"), "r") 213 | with data as f: 214 | filecontent = yaml.safe_load(f) 215 | entity_cards.update({fname.replace(".yaml",""): filecontent}) 216 | 217 | entities_popup = {} 218 | if os.path.isdir(hass.config.path("dwains-dashboard/configs/cards/entities_popup")): 219 | #for fname in await listdir_async(hass.config.path("dwains-dashboard/configs/cards/entities_popup")): 220 | fnames = await hass.async_add_executor_job(os.listdir, hass.config.path("dwains-dashboard/configs/cards/entities_popup")) 221 | for fname in fnames: 222 | if fname.endswith('.yaml'): 223 | #_LOGGER.warning(f"websocket_get_configuration() entities_popup: {fname}") 224 | #file_path = hass.config.path(f"dwains-dashboard/configs/cards/entities_popup/{fname}") 225 | #filecontent = await read_yaml_file(file_path) 226 | data = await hass.async_add_executor_job(open, hass.config.path(f"dwains-dashboard/configs/cards/entities_popup/{fname}"), "r") 227 | with data as f: 228 | filecontent = yaml.safe_load(f) 229 | entities_popup.update({fname.replace(".yaml",""): filecontent}) 230 | 231 | devices_card = {} 232 | path_devices_card = hass.config.path("dwains-dashboard/configs/cards/devices_card") 233 | if os.path.isdir(path_devices_card): 234 | #for fname in await listdir_async(hass.config.path("dwains-dashboard/configs/cards/devices_card")): 235 | fnames = await hass.async_add_executor_job(os.listdir, hass.config.path("dwains-dashboard/configs/cards/devices_card")) 236 | for fname in fnames: 237 | if fname.endswith('.yaml'): 238 | #_LOGGER.warning(f"websocket_get_configuration() devices_card: {fname}") 239 | #file_path = hass.config.path(f"dwains-dashboard/configs/cards/devices_card/{fname}") 240 | #filecontent = await read_yaml_file(file_path) 241 | data = await hass.async_add_executor_job(open, hass.config.path(f"dwains-dashboard/configs/cards/devices_card/{fname}"), "r") 242 | with data as f: 243 | filecontent = yaml.safe_load(f) 244 | devices_card.update({fname.replace(".yaml",""): filecontent}) 245 | 246 | devices_popup = {} 247 | if os.path.isdir(hass.config.path("dwains-dashboard/configs/cards/devices_popup")): 248 | #for fname in await listdir_async(hass.config.path("dwains-dashboard/configs/cards/devices_popup")): 249 | fnames = await hass.async_add_executor_job(os.listdir, hass.config.path("dwains-dashboard/configs/cards/devices_popup")) 250 | for fname in fnames: 251 | if fname.endswith('.yaml'): 252 | #_LOGGER.warning(f"websocket_get_configuration() devices_popup: {fname}") 253 | #file_path = hass.config.path(f"dwains-dashboard/configs/cards/devices_popup/{fname}") 254 | #filecontent = await read_yaml_file(file_path) 255 | data = await hass.async_add_executor_job(open, hass.config.path(f"dwains-dashboard/configs/cards/devices_popup/{fname}"), "r") 256 | with data as f: 257 | filecontent = yaml.safe_load(f) 258 | devices_popup.update({fname.replace(".yaml",""): filecontent}) 259 | 260 | more_pages = {} 261 | if os.path.isdir(hass.config.path("dwains-dashboard/configs/more_pages")): 262 | #for subdir in await listdir_async(hass.config.path("dwains-dashboard/configs/more_pages")): 263 | subdirs = await hass.async_add_executor_job(os.listdir, hass.config.path("dwains-dashboard/configs/more_pages")) 264 | for subdir in subdirs: 265 | if (os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml"))) and (os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml"))): 266 | if fname.endswith('.yaml'): 267 | #_LOGGER.warning(f"websocket_get_configuration() more_pages: {fname}") 268 | data = await hass.async_add_executor_job(open, hass.config.path(f"dwains-dashboard/configs/more_pages/{subdir}/config.yaml"), "r") 269 | with data as f: 270 | filecontent = yaml.safe_load(f) 271 | more_pages[subdir] = filecontent 272 | 273 | #_LOGGER.warning(f"websocket_get_configuration() {cards}") 274 | 275 | connection.send_result( 276 | msg["id"], 277 | { 278 | "areas": areas, 279 | "area_cards": area_cards, 280 | "device_cards": device_cards, 281 | "entity_cards": entity_cards, 282 | "entities_popup": entities_popup, 283 | "entities": entities, 284 | "devices": devices, 285 | "homepage_header": homepage_header, 286 | "more_pages": more_pages, 287 | "installed_version": VERSION, 288 | "devices_card": devices_card, 289 | "devices_popup": devices_popup, 290 | 291 | } 292 | ) 293 | 294 | 295 | #get_blueprints 296 | #at callback -> websocket_api.async_response 297 | @websocket_api.async_response 298 | @websocket_api.websocket_command({vol.Required("type"): "dwains_dashboard/get_blueprints"}) 299 | async def websocket_get_blueprints( 300 | hass: HomeAssistant, 301 | connection: websocket_api.ActiveConnection, 302 | msg: Mapping[str, Any], 303 | ) -> None: 304 | """Return a list of installed blueprints asynchronously.""" 305 | 306 | blueprints = {} 307 | 308 | blueprints_dir = hass.config.path("dwains-dashboard/blueprints") 309 | 310 | #if os.path.isdir(hass.config.path("dwains-dashboard/blueprints")): 311 | if await hass.async_add_executor_job(os.path.isdir, blueprints_dir): 312 | #for fname in os.listdir(hass.config.path("dwains-dashboard/blueprints")): 313 | file_list = await hass.async_add_executor_job(os.listdir, blueprints_dir) 314 | 315 | for fname in file_list: 316 | if fname.endswith(".yaml"): 317 | file_path = os.path.join(blueprints_dir, fname) 318 | 319 | try: 320 | # Open the file asynchronously by using async_add_executor_job 321 | data = await hass.async_add_executor_job(open, file_path, "r") 322 | with data as f: 323 | filecontent = yaml.safe_load(f) 324 | blueprints[fname] = filecontent 325 | 326 | except Exception as e: 327 | _LOGGER.error(f"Error loading blueprint {fname}: {e}") 328 | 329 | connection.send_result( 330 | msg["id"], 331 | { 332 | "blueprints": blueprints, 333 | } 334 | ) 335 | 336 | 337 | #install_blueprint 338 | @websocket_api.websocket_command( 339 | { 340 | vol.Required("type"): "dwains_dashboard/install_blueprint", 341 | vol.Required("yamlCode"): str, 342 | } 343 | ) 344 | @websocket_api.async_response 345 | async def ws_handle_install_blueprint( 346 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 347 | ) -> None: 348 | """Handle save new blueprint.""" 349 | 350 | #PR 817 351 | #filecontent = yaml.safe_load(json.loads(msg["yamlCode"])) 352 | filecontent = yaml.safe_load(msg["yamlCode"]) 353 | 354 | if not filecontent.get("blueprint"): 355 | _LOGGER.warning('no blueprint data') 356 | connection.send_result( 357 | msg["id"], 358 | { 359 | "error": "Blueprint has invalid data" 360 | }, 361 | ) 362 | return 363 | 364 | if not filecontent.get("card"): 365 | _LOGGER.warning('no card') 366 | connection.send_result( 367 | msg["id"], 368 | { 369 | "error": "Blueprint has no card" 370 | }, 371 | ) 372 | return 373 | 374 | filename = slugify(filecontent["blueprint"]["name"])+".yaml" 375 | 376 | if filecontent.get("button_card_templates"): 377 | if not os.path.exists(hass.config.path("dwains-dashboard/button_card_templates/blueprints")): 378 | os.makedirs(hass.config.path("dwains-dashboard/button_card_templates/blueprints")) 379 | 380 | #with open(hass.config.path("dwains-dashboard/button_card_templates/blueprints/"+filename), 'w') as f: 381 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/button_card_templates/blueprints/"+filename), "w") 382 | with data as f: 383 | yaml.dump(filecontent.get("button_card_templates"), f, default_flow_style=False, sort_keys=False) 384 | 385 | filecontent.pop("button_card_templates") 386 | 387 | if filecontent.get("apexcharts_card_templates"): 388 | if not os.path.exists(hass.config.path("dwains-dashboard/apexcharts_card_templates/blueprints")): 389 | os.makedirs(hass.config.path("dwains-dashboard/apexcharts_card_templates/blueprints")) 390 | 391 | #with open(hass.config.path("dwains-dashboard/apexcharts_card_templates/blueprints/"+filename), 'w') as f: 392 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/apexcharts_card_templates/blueprints/"+filename), "w") 393 | with data as f: 394 | yaml.dump(filecontent.get("apexcharts_card_templates"), f, default_flow_style=False, sort_keys=False) 395 | 396 | filecontent.pop("apexcharts_card_templates") 397 | 398 | if not os.path.exists(hass.config.path("dwains-dashboard/blueprints")): 399 | os.makedirs(hass.config.path("dwains-dashboard/blueprints")) 400 | 401 | #with open(hass.config.path("dwains-dashboard/blueprints/"+filename), 'w') as f: 402 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/blueprints/"+filename), "w") 403 | with data as f: 404 | yaml.dump(filecontent, f, default_flow_style=False, sort_keys=False) 405 | 406 | connection.send_result( 407 | msg["id"], 408 | { 409 | "succesfull": filename 410 | }, 411 | ) 412 | 413 | 414 | 415 | 416 | #delete_blueprint 417 | @websocket_api.websocket_command( 418 | { 419 | vol.Required("type"): "dwains_dashboard/delete_blueprint", 420 | vol.Required("blueprint"): str, 421 | } 422 | ) 423 | @websocket_api.async_response 424 | async def ws_handle_delete_blueprint( 425 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 426 | ) -> None: 427 | """Handle delete blueprint.""" 428 | 429 | filename = hass.config.path("dwains-dashboard/blueprints/"+msg["blueprint"]) 430 | 431 | if os.path.exists(filename): 432 | os.remove(filename) 433 | 434 | connection.send_result( 435 | msg["id"], 436 | { 437 | "succesfull": "Blueprint deleted succesfull" 438 | }, 439 | ) 440 | 441 | 442 | 443 | #edit_area_button 444 | @websocket_api.websocket_command( 445 | { 446 | vol.Required("type"): "dwains_dashboard/edit_area_button", 447 | vol.Optional("icon"): str, 448 | vol.Optional("areaId"): str, 449 | vol.Optional("floor"): str, 450 | vol.Optional("disableArea"): bool, 451 | } 452 | ) 453 | @websocket_api.async_response 454 | async def ws_handle_edit_area_button( 455 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 456 | ) -> None: 457 | """Handle saving editing area button.""" 458 | #_LOGGER.warning(f"ws_handle_edit_area_button() called.") 459 | 460 | if(msg["areaId"]): 461 | #_LOGGER.warning(f"Editing area: {msg["areaId"]}") 462 | 463 | if os.path.exists(hass.config.path("dwains-dashboard/configs/areas.yaml")): 464 | #with open(hass.config.path("dwains-dashboard/configs/areas.yaml")) as f: 465 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/areas.yaml", "r") 466 | with data as f: 467 | areas = yaml.safe_load(f) 468 | else: 469 | areas = OrderedDict() 470 | 471 | area = areas.get(msg["areaId"]) 472 | 473 | if not area: 474 | areas[msg["areaId"]] = OrderedDict() 475 | 476 | areas[msg["areaId"]].update({ 477 | "icon": msg["icon"], 478 | "floor": msg["floor"], 479 | "disabled": msg["disableArea"], 480 | }) 481 | 482 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 483 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 484 | 485 | #with open(hass.config.path("dwains-dashboard/configs/areas.yaml"), 'w') as f: 486 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/areas.yaml", "w") 487 | with data as f: 488 | yaml.dump(areas, f, default_flow_style=False, sort_keys=False) 489 | 490 | if areas: 491 | areas = await hass.async_add_executor_job( 492 | lambda: yaml.safe_load(open(hass.config.path("dwains-dashboard/configs/areas.yaml"), "r")) 493 | ) 494 | 495 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 496 | 497 | connection.send_result( 498 | msg["id"], 499 | { 500 | "succesfull": "Area button saved" 501 | }, 502 | ) 503 | 504 | 505 | 506 | #edit_area_bool_value 507 | @websocket_api.websocket_command( 508 | { 509 | vol.Required("type"): "dwains_dashboard/edit_area_bool_value", 510 | vol.Required("areaId"): str, 511 | vol.Optional("key"): str, 512 | vol.Optional("value"): bool, 513 | } 514 | ) 515 | @websocket_api.async_response 516 | async def ws_handle_edit_area_bool_value( 517 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 518 | ) -> None: 519 | """Handle edit area bool value command.""" 520 | 521 | if os.path.exists(hass.config.path("dwains-dashboard/configs/areas.yaml")): 522 | #with open(hass.config.path("dwains-dashboard/configs/areas.yaml")) as f: 523 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/areas.yaml", "r") 524 | with data as f: 525 | areas = yaml.safe_load(f) 526 | else: 527 | areas = OrderedDict() 528 | 529 | area = areas.get(msg["areaId"]) 530 | 531 | if not area: 532 | areas[msg["areaId"]] = OrderedDict() 533 | 534 | areas[msg["areaId"]].update({ 535 | msg["key"]: msg["value"] 536 | }) 537 | 538 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 539 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 540 | 541 | #with open(hass.config.path("dwains-dashboard/configs/areas.yaml"), 'w') as f: 542 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/areas.yaml", "w") 543 | with data as f: 544 | yaml.dump(areas, f, default_flow_style=False, sort_keys=False) 545 | 546 | 547 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 548 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 549 | 550 | connection.send_result( 551 | msg["id"], 552 | { 553 | "succesfull": "Area bool value set succesfully" 554 | }, 555 | ) 556 | 557 | 558 | 559 | 560 | #edit_homepage_header 561 | @websocket_api.websocket_command( 562 | { 563 | vol.Required("type"): "dwains_dashboard/edit_homepage_header", 564 | vol.Optional("disableClock"): bool, 565 | vol.Optional("amPmClock"): bool, 566 | vol.Optional("disableWelcomeMessage"): bool, 567 | vol.Optional("v2Mode"): bool, 568 | vol.Optional("disableSensorGraph"): bool, 569 | vol.Optional("weatherEntity"): str, 570 | vol.Optional("invertCover"): bool, 571 | vol.Optional("alarmEntity"): str, 572 | 573 | } 574 | ) 575 | @websocket_api.async_response 576 | async def ws_handle_edit_homepage_header( 577 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 578 | ) -> None: 579 | """Handle saving editing homepage header.""" 580 | 581 | if os.path.exists(hass.config.path("dwains-dashboard/configs/settings.yaml")): 582 | #with open(hass.config.path("dwains-dashboard/configs/settings.yaml")) as f: 583 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/settings.yaml", "r") 584 | with data as f: 585 | homepage_header = yaml.safe_load(f) 586 | else: 587 | homepage_header = OrderedDict() 588 | 589 | homepage_header.update({ 590 | "disable_clock": msg["disableClock"], 591 | "am_pm_clock": msg["amPmClock"], 592 | "disable_welcome_message": msg["disableWelcomeMessage"], 593 | "v2_mode": msg["v2Mode"], 594 | "disable_sensor_graph": msg["disableSensorGraph"], 595 | "invert_cover": msg["invertCover"], 596 | "weather_entity": msg["weatherEntity"], 597 | "alarm_entity": msg["alarmEntity"], 598 | }) 599 | 600 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 601 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 602 | 603 | #with open(hass.config.path("dwains-dashboard/configs/settings.yaml"), 'w') as f: 604 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/settings.yaml", "w") 605 | with data as f: 606 | yaml.dump(homepage_header, f, default_flow_style=False, sort_keys=False) 607 | 608 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 609 | 610 | connection.send_result( 611 | msg["id"], 612 | { 613 | "succesfull": "Homepage header saved" 614 | }, 615 | ) 616 | 617 | 618 | #edit_device_button 619 | @websocket_api.websocket_command( 620 | { 621 | vol.Required("type"): "dwains_dashboard/edit_device_button", 622 | vol.Optional("icon"): str, 623 | vol.Optional("device"): str, 624 | vol.Optional("showInNavbar"): bool, 625 | } 626 | ) 627 | @websocket_api.async_response 628 | async def ws_handle_edit_device_button( 629 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 630 | ) -> None: 631 | """Handle saving editing area button.""" 632 | 633 | if(msg["device"]): 634 | if os.path.exists(hass.config.path("dwains-dashboard/configs/devices.yaml")): 635 | #with open(hass.config.path("dwains-dashboard/configs/devices.yaml")) as f: 636 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/devices.yaml", "r") 637 | with data as f: 638 | devices = yaml.safe_load(f) 639 | else: 640 | devices = OrderedDict() 641 | 642 | device = devices.get(msg["device"]) 643 | 644 | if not device: 645 | devices[msg["device"]] = OrderedDict() 646 | 647 | devices[msg["device"]].update({ 648 | "icon": msg["icon"], 649 | "show_in_navbar": msg["showInNavbar"], 650 | }) 651 | 652 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 653 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 654 | 655 | #with open(hass.config.path("dwains-dashboard/configs/devices.yaml"), 'w') as f: 656 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/devices.yaml", "w") 657 | with data as f: 658 | yaml.dump(devices, f, default_flow_style=False, sort_keys=False) 659 | 660 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 661 | hass.bus.async_fire("dwains_dashboard_navigation_card_reload") 662 | 663 | connection.send_result( 664 | msg["id"], 665 | { 666 | "succesfull": "Device button saved" 667 | }, 668 | ) 669 | 670 | 671 | 672 | 673 | #edit_device_card 674 | @websocket_api.websocket_command( 675 | { 676 | vol.Required("type"): "dwains_dashboard/edit_device_card", 677 | vol.Required("cardData"): str, 678 | vol.Required("domain"): str, 679 | } 680 | ) 681 | @websocket_api.async_response 682 | async def ws_handle_edit_device_card( 683 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 684 | ) -> None: 685 | """Handle saving device card.""" 686 | 687 | filecontent = json.loads(msg["cardData"]) 688 | 689 | path = "dwains-dashboard/configs/cards/devices_card/" 690 | filename = hass.config.path(path+"/"+msg['domain']+".yaml") 691 | 692 | os.makedirs(os.path.dirname(filename), exist_ok=True) # Create the folder if not exists 693 | 694 | #ff = open(filename, 'w+') 695 | data = await hass.async_add_executor_job(open, filename, "w+") 696 | with data as ff: 697 | yaml.dump(yaml.safe_load(json.dumps(filecontent)), ff, default_flow_style=False) 698 | 699 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 700 | 701 | connection.send_result( 702 | msg["id"], 703 | { 704 | "succesfull": "Device card saved" 705 | }, 706 | ) 707 | 708 | 709 | 710 | #remove_device_card 711 | @websocket_api.websocket_command( 712 | { 713 | vol.Required("type"): "dwains_dashboard/remove_device_card", 714 | vol.Required("domain"): str, 715 | } 716 | ) 717 | @websocket_api.async_response 718 | async def ws_handle_remove_device_card( 719 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 720 | ) -> None: 721 | """Handle remove domain card command.""" 722 | 723 | path = "dwains-dashboard/configs/cards/devices_card" 724 | filename = hass.config.path(path+"/"+msg["domain"]+".yaml") 725 | 726 | if os.path.exists(filename): 727 | os.remove(filename) 728 | 729 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 730 | 731 | connection.send_result( 732 | msg["id"], 733 | { 734 | "succesfull": "Entity card removed succesfully" 735 | }, 736 | ) 737 | 738 | 739 | #edit_device_popup 740 | @websocket_api.websocket_command( 741 | { 742 | vol.Required("type"): "dwains_dashboard/edit_device_popup", 743 | vol.Required("cardData"): str, 744 | vol.Required("domain"): str, 745 | } 746 | ) 747 | @websocket_api.async_response 748 | async def ws_handle_edit_device_popup( 749 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 750 | ) -> None: 751 | """Handle saving device popup.""" 752 | 753 | filecontent = json.loads(msg["cardData"]) 754 | 755 | path = "dwains-dashboard/configs/cards/devices_popup/" 756 | filename = hass.config.path(path+"/"+msg['domain']+".yaml") 757 | 758 | os.makedirs(os.path.dirname(filename), exist_ok=True) # Create the folder if not exists 759 | 760 | #ff = open(filename, 'w+') 761 | data = await hass.async_add_executor_job(open, filename, "w+") 762 | with data as ff: 763 | yaml.dump(yaml.safe_load(json.dumps(filecontent)), ff, default_flow_style=False) 764 | 765 | hass.bus.async_fire("dwains_dashboard_reload") 766 | 767 | connection.send_result( 768 | msg["id"], 769 | { 770 | "succesfull": "Device popup saved" 771 | }, 772 | ) 773 | 774 | 775 | 776 | #remove_device_popup 777 | @websocket_api.websocket_command( 778 | { 779 | vol.Required("type"): "dwains_dashboard/remove_device_popup", 780 | vol.Required("domain"): str, 781 | } 782 | ) 783 | @websocket_api.async_response 784 | async def ws_handle_remove_device_popup( 785 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 786 | ) -> None: 787 | """Handle remove domain popup command.""" 788 | 789 | path = "dwains-dashboard/configs/cards/devices_popup" 790 | filename = hass.config.path(path+"/"+msg["domain"]+".yaml") 791 | 792 | if os.path.exists(filename): 793 | os.remove(filename) 794 | 795 | hass.bus.async_fire("dwains_dashboard_reload") 796 | 797 | connection.send_result( 798 | msg["id"], 799 | { 800 | "succesfull": "Device popup removed succesfully" 801 | }, 802 | ) 803 | 804 | 805 | 806 | #remove_entity_card 807 | @websocket_api.websocket_command( 808 | { 809 | vol.Required("type"): "dwains_dashboard/remove_entity_card", 810 | vol.Required("entityId"): str, 811 | } 812 | ) 813 | @websocket_api.async_response 814 | async def ws_handle_remove_entity_card( 815 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 816 | ) -> None: 817 | """Handle remove entity card command.""" 818 | 819 | path = "dwains-dashboard/configs/cards/entities" 820 | filename = hass.config.path(path+"/"+msg["entityId"]+".yaml") 821 | 822 | if os.path.exists(filename): 823 | os.remove(filename) 824 | 825 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 826 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 827 | 828 | connection.send_result( 829 | msg["id"], 830 | { 831 | "succesfull": "Entity card removed succesfully" 832 | }, 833 | ) 834 | 835 | 836 | #remove_entity_popup 837 | @websocket_api.websocket_command( 838 | { 839 | vol.Required("type"): "dwains_dashboard/remove_entity_popup", 840 | vol.Required("entityId"): str, 841 | } 842 | ) 843 | @websocket_api.async_response 844 | async def ws_handle_remove_entity_popup( 845 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 846 | ) -> None: 847 | """Handle remove entity card command.""" 848 | 849 | path = "dwains-dashboard/configs/cards/entities_popup" 850 | filename = hass.config.path(path+"/"+msg["entityId"]+".yaml") 851 | 852 | if os.path.exists(filename): 853 | os.remove(filename) 854 | 855 | hass.bus.async_fire("dwains_dashboard_reload") 856 | 857 | connection.send_result( 858 | msg["id"], 859 | { 860 | "succesfull": "Entity card removed succesfully" 861 | }, 862 | ) 863 | 864 | 865 | 866 | #edit_entity 867 | @websocket_api.websocket_command( 868 | { 869 | vol.Required("type"): "dwains_dashboard/edit_entity", 870 | vol.Required("entity"): str, 871 | vol.Optional("friendlyName"): str, 872 | vol.Optional("disableEntity"): bool, 873 | vol.Optional("hideEntity"): bool, 874 | vol.Optional("excludeEntity"): bool, 875 | vol.Optional("rowSpan"): str, 876 | vol.Optional("colSpan"): str, 877 | vol.Optional("rowSpanLg"): str, 878 | vol.Optional("colSpanLg"): str, 879 | vol.Optional("rowSpanXl"): str, 880 | vol.Optional("colSpanXl"): str, 881 | vol.Optional("customCard"): bool, 882 | vol.Optional("customPopup"): bool, 883 | } 884 | ) 885 | @websocket_api.async_response 886 | async def ws_handle_edit_entity( 887 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 888 | ) -> None: 889 | """Handle saving editing entity.""" 890 | 891 | if os.path.exists(hass.config.path("dwains-dashboard/configs/entities.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/entities.yaml")).st_size != 0: 892 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml")) as f: 893 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/entities.yaml"), "r") 894 | with data as f: 895 | entities = yaml.safe_load(f) 896 | else: 897 | entities = OrderedDict() 898 | 899 | entity = entities.get(msg["entity"]) 900 | 901 | if not entity: 902 | entities[msg["entity"]] = OrderedDict() 903 | 904 | entities[msg["entity"]].update({ 905 | "hidden": msg["hideEntity"], 906 | "excluded": msg["excludeEntity"], 907 | "disabled": msg["disableEntity"], 908 | "friendly_name": msg["friendlyName"], 909 | "col_span": msg["colSpan"], 910 | "row_span": msg["rowSpan"], 911 | "col_span_lg": msg["colSpanLg"], 912 | "row_span_lg": msg["rowSpanLg"], 913 | "col_span_xl": msg["colSpanXl"], 914 | "row_span_xl": msg["rowSpanXl"], 915 | "custom_card": msg["customCard"], 916 | "custom_popup": msg["customPopup"], 917 | }) 918 | 919 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 920 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 921 | 922 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml"), 'w') as f: 923 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "w") 924 | with data as f: 925 | yaml.dump(entities, f, default_flow_style=False, sort_keys=False) 926 | 927 | 928 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 929 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 930 | 931 | connection.send_result( 932 | msg["id"], 933 | { 934 | "succesfull": "Entity saved" 935 | }, 936 | ) 937 | 938 | 939 | 940 | #edit_entity_card 941 | @websocket_api.websocket_command( 942 | { 943 | vol.Required("type"): "dwains_dashboard/edit_entity_card", 944 | vol.Required("cardData"): str, 945 | vol.Required("entityId"): str, 946 | } 947 | ) 948 | @websocket_api.async_response 949 | async def ws_handle_edit_entity_card( 950 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 951 | ) -> None: 952 | """Handle edit entity card command.""" 953 | 954 | filecontent = json.loads(msg["cardData"]) 955 | 956 | path = "dwains-dashboard/configs/cards/entities/" 957 | filename = hass.config.path(path+"/"+msg['entityId']+".yaml") 958 | 959 | os.makedirs(os.path.dirname(filename), exist_ok=True) # Create the folder if not exists 960 | 961 | #ff = open(filename, 'w+') 962 | data = await hass.async_add_executor_job(open, filename, "w+") 963 | with data as ff: 964 | yaml.dump(yaml.safe_load(json.dumps(filecontent)), ff, default_flow_style=False) 965 | 966 | #Enable use custom card for the entity settings by default 967 | if os.path.exists(hass.config.path("dwains-dashboard/configs/entities.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/entities.yaml")).st_size != 0: 968 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml")) as f: 969 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/entities.yaml"), "r") 970 | with data as f: 971 | entities = yaml.safe_load(f) 972 | else: 973 | entities = OrderedDict() 974 | 975 | entity = entities.get(msg["entityId"]) 976 | 977 | if not entity: 978 | entities[msg["entityId"]] = OrderedDict() 979 | 980 | entities[msg["entityId"]].update({ 981 | "custom_card": True, 982 | }) 983 | 984 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 985 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 986 | 987 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml"), 'w') as f: 988 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "w") 989 | with data as f: 990 | yaml.dump(entities, f, default_flow_style=False, sort_keys=False) 991 | 992 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 993 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 994 | 995 | connection.send_result( 996 | msg["id"], 997 | { 998 | "succesfull": "Card added succesfully" 999 | }, 1000 | ) 1001 | 1002 | 1003 | #edit_entity_popup 1004 | @websocket_api.websocket_command( 1005 | { 1006 | vol.Required("type"): "dwains_dashboard/edit_entity_popup", 1007 | vol.Required("cardData"): str, 1008 | vol.Required("entityId"): str, 1009 | } 1010 | ) 1011 | @websocket_api.async_response 1012 | async def ws_handle_edit_entity_popup( 1013 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1014 | ) -> None: 1015 | """Handle edit entity popup command.""" 1016 | 1017 | filecontent = json.loads(msg["cardData"]) 1018 | 1019 | path = "dwains-dashboard/configs/cards/entities_popup/" 1020 | filename = hass.config.path(path+"/"+msg['entityId']+".yaml") 1021 | 1022 | os.makedirs(os.path.dirname(filename), exist_ok=True) # Create the folder if not exists 1023 | 1024 | #ff = open(filename, 'w+') 1025 | data = await hass.async_add_executor_job(open, filename, "w+") 1026 | with data as ff: 1027 | yaml.dump(yaml.safe_load(json.dumps(filecontent)), ff, default_flow_style=False) 1028 | 1029 | #Enable use custom card for the entity settings by default 1030 | if os.path.exists(hass.config.path("dwains-dashboard/configs/entities.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/entities.yaml")).st_size != 0: 1031 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml")) as f: 1032 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/entities.yaml"), "r") 1033 | with data as f: 1034 | entities = yaml.safe_load(f) 1035 | else: 1036 | entities = OrderedDict() 1037 | 1038 | entity = entities.get(msg["entityId"]) 1039 | 1040 | if not entity: 1041 | entities[msg["entityId"]] = OrderedDict() 1042 | 1043 | entities[msg["entityId"]].update({ 1044 | "custom_popup": True, 1045 | }) 1046 | 1047 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 1048 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 1049 | 1050 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml"), 'w') as f: 1051 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "w") 1052 | with data as f: 1053 | yaml.dump(entities, f, default_flow_style=False, sort_keys=False) 1054 | 1055 | hass.bus.async_fire("dwains_dashboard_reload") 1056 | 1057 | connection.send_result( 1058 | msg["id"], 1059 | { 1060 | "succesfull": "Popup added succesfully" 1061 | }, 1062 | ) 1063 | 1064 | 1065 | 1066 | #edit_entity_favorite 1067 | @websocket_api.websocket_command( 1068 | { 1069 | vol.Required("type"): "dwains_dashboard/edit_entity_favorite", 1070 | vol.Required("entityId"): str, 1071 | vol.Optional("favorite"): bool, 1072 | } 1073 | ) 1074 | @websocket_api.async_response 1075 | async def ws_handle_edit_entity_favorite( 1076 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1077 | ) -> None: 1078 | """Handle edit entity favorite command.""" 1079 | 1080 | if os.path.exists(hass.config.path("dwains-dashboard/configs/entities.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/entities.yaml")).st_size != 0: 1081 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml")) as f: 1082 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "r") 1083 | with data as f: 1084 | entities = yaml.safe_load(f) 1085 | else: 1086 | entities = OrderedDict() 1087 | 1088 | entity = entities.get(msg["entityId"]) 1089 | 1090 | if not entity: 1091 | entities[msg["entityId"]] = OrderedDict() 1092 | 1093 | entities[msg["entityId"]].update({ 1094 | "favorite": msg["favorite"] 1095 | }) 1096 | 1097 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 1098 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 1099 | 1100 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml"), 'w') as f: 1101 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "w") 1102 | with data as f: 1103 | yaml.dump(entities, f, default_flow_style=False, sort_keys=False) 1104 | 1105 | 1106 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 1107 | 1108 | connection.send_result( 1109 | msg["id"], 1110 | { 1111 | "succesfull": "Popup added succesfully" 1112 | }, 1113 | ) 1114 | 1115 | 1116 | #edit_entity_bool_value 1117 | @websocket_api.websocket_command( 1118 | { 1119 | vol.Required("type"): "dwains_dashboard/edit_entity_bool_value", 1120 | vol.Required("entityId"): str, 1121 | vol.Optional("key"): str, 1122 | vol.Optional("value"): bool, 1123 | } 1124 | ) 1125 | @websocket_api.async_response 1126 | async def ws_handle_edit_entity_bool_value( 1127 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1128 | ) -> None: 1129 | """Handle edit entity bool value command.""" 1130 | 1131 | if os.path.exists(hass.config.path("dwains-dashboard/configs/entities.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/entities.yaml")).st_size != 0: 1132 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml")) as f: 1133 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "r") 1134 | with data as f: 1135 | entities = yaml.safe_load(f) 1136 | else: 1137 | entities = OrderedDict() 1138 | 1139 | entity = entities.get(msg["entityId"]) 1140 | 1141 | if not entity: 1142 | entities[msg["entityId"]] = OrderedDict() 1143 | 1144 | entities[msg["entityId"]].update({ 1145 | msg["key"]: msg["value"] 1146 | }) 1147 | 1148 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 1149 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 1150 | 1151 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml"), 'w') as f: 1152 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "w") 1153 | with data as f: 1154 | yaml.dump(entities, f, default_flow_style=False, sort_keys=False) 1155 | 1156 | 1157 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 1158 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 1159 | 1160 | connection.send_result( 1161 | msg["id"], 1162 | { 1163 | "succesfull": "Entity bool value set succesfully" 1164 | }, 1165 | ) 1166 | 1167 | 1168 | 1169 | #edit_entities_bool_value 1170 | @websocket_api.websocket_command( 1171 | { 1172 | vol.Required("type"): "dwains_dashboard/edit_entities_bool_value", 1173 | vol.Required("entities"): str, 1174 | vol.Optional("key"): str, 1175 | vol.Optional("value"): bool, 1176 | } 1177 | ) 1178 | @websocket_api.async_response 1179 | async def ws_handle_edit_entities_bool_value( 1180 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1181 | ) -> None: 1182 | """Handle edit entities bool value command.""" 1183 | 1184 | if os.path.exists(hass.config.path("dwains-dashboard/configs/entities.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/entities.yaml")).st_size != 0: 1185 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml")) as f: 1186 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "r") 1187 | with data as f: 1188 | entities = yaml.safe_load(f) 1189 | else: 1190 | entities = OrderedDict() 1191 | 1192 | entitiesInput = json.loads(msg["entities"]) 1193 | 1194 | _LOGGER.warning(entitiesInput) 1195 | 1196 | for num, entityId in enumerate(entitiesInput, start=1): 1197 | entity = entities.get(entityId) 1198 | 1199 | if not entity: 1200 | entities[entityId] = OrderedDict() 1201 | 1202 | entities[entityId].update({ 1203 | msg["key"]: msg["value"] 1204 | }) 1205 | 1206 | _LOGGER.warning(entities) 1207 | 1208 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 1209 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 1210 | 1211 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml"), 'w') as f: 1212 | data = await hass.async_add_executor_job(open, "dwains-dashboard/configs/entities.yaml", "w") 1213 | with data as f: 1214 | yaml.dump(entities, f, default_flow_style=False, sort_keys=False) 1215 | 1216 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 1217 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 1218 | 1219 | connection.send_result( 1220 | msg["id"], 1221 | { 1222 | "succesfull": "Entities bool value set succesfully" 1223 | }, 1224 | ) 1225 | 1226 | #add_card 1227 | @websocket_api.websocket_command( 1228 | { 1229 | vol.Required("type"): "dwains_dashboard/add_card", 1230 | vol.Optional("card_data"): str, 1231 | vol.Optional("area_id"): str, 1232 | vol.Optional("domain"): str, 1233 | vol.Optional("position"): str, 1234 | vol.Optional("filename"): str, 1235 | vol.Optional("page"): str, 1236 | vol.Optional("rowSpan"): str, 1237 | vol.Optional("colSpan"): str, 1238 | vol.Optional("rowSpanLg"): str, 1239 | vol.Optional("colSpanLg"): str, 1240 | vol.Optional("rowSpanXl"): str, 1241 | vol.Optional("colSpanXl"): str, 1242 | 1243 | } 1244 | ) 1245 | @websocket_api.async_response 1246 | async def ws_handle_add_card( 1247 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1248 | ) -> None: 1249 | """Handle add new card command.""" 1250 | 1251 | if not msg["filename"]: 1252 | type = json.loads(msg["card_data"])['type'] 1253 | else: 1254 | type = msg["filename"] 1255 | 1256 | if type: 1257 | filecontent = json.loads(msg["card_data"]) 1258 | 1259 | #filecontent.update({"position": msg["position"]}) 1260 | filecontent["col_span"] = msg["colSpan"] 1261 | filecontent["row_span"] = msg["rowSpan"] 1262 | filecontent["col_span_lg"] = msg["colSpanLg"] 1263 | filecontent["row_span_lg"] = msg["rowSpanLg"] 1264 | filecontent["col_span_xl"] = msg["colSpanXl"] 1265 | filecontent["row_span_xl"] = msg["rowSpanXl"] 1266 | filecontent['position'] = msg["position"] 1267 | 1268 | if(msg["page"] == 'areas'): 1269 | path = "dwains-dashboard/configs/cards/areas/"+msg['area_id'] 1270 | elif(msg["page"] == 'devices'): 1271 | path = "dwains-dashboard/configs/cards/devices/"+msg['domain'] 1272 | filename = hass.config.path(path+"/"+type+".yaml") 1273 | 1274 | os.makedirs(os.path.dirname(filename), exist_ok=True) # Create the folder if not exists 1275 | 1276 | if not msg["filename"]: 1277 | if os.path.exists(filename) and os.stat(filename).st_size != 0: 1278 | filename = hass.config.path(path+"/"+type+datetime.now().strftime("%Y%m%d%H%M%S")+".yaml") 1279 | os.makedirs(os.path.dirname(filename), exist_ok=True) 1280 | 1281 | 1282 | #ff = open(filename, 'w+') 1283 | data = await hass.async_add_executor_job(open, filename, "w+") 1284 | with data as ff: 1285 | yaml.dump(yaml.safe_load(json.dumps(filecontent)), ff, default_flow_style=False) 1286 | 1287 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 1288 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 1289 | 1290 | connection.send_result( 1291 | msg["id"], 1292 | { 1293 | "succesfull": "card added succesfully" 1294 | }, 1295 | ) 1296 | 1297 | #remove_card 1298 | @websocket_api.websocket_command( 1299 | { 1300 | vol.Required("type"): "dwains_dashboard/remove_card", 1301 | vol.Optional("area_id"): str, 1302 | vol.Optional("domain"): str, 1303 | vol.Optional("filename"): str, 1304 | vol.Optional("page"): str, 1305 | } 1306 | ) 1307 | @websocket_api.async_response 1308 | async def ws_handle_remove_card( 1309 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1310 | ) -> None: 1311 | """Handle remove card command.""" 1312 | 1313 | if(msg["domain"]): 1314 | path = "dwains-dashboard/configs/cards/devices/"+msg['domain'] 1315 | else: 1316 | path = "dwains-dashboard/configs/cards/areas/"+msg['area_id'] 1317 | 1318 | filename = hass.config.path(path+"/"+msg["filename"]+".yaml") 1319 | 1320 | if os.path.exists(filename): 1321 | os.remove(filename) 1322 | 1323 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 1324 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 1325 | 1326 | connection.send_result( 1327 | msg["id"], 1328 | { 1329 | "succesfull": "card removed succesfully" 1330 | }, 1331 | ) 1332 | 1333 | 1334 | #edit_more_page_button 1335 | #NOT USED 1336 | @websocket_api.websocket_command( 1337 | { 1338 | vol.Required("type"): "dwains_dashboard/edit_more_page_button", 1339 | vol.Optional("more_page"): str, 1340 | vol.Optional("name"): str, 1341 | vol.Optional("icon"): str, 1342 | vol.Optional("showInNavbar"): bool, 1343 | } 1344 | ) 1345 | @websocket_api.async_response 1346 | async def ws_handle_edit_more_page_button( 1347 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1348 | ) -> None: 1349 | """Handle saving editing more page button.""" 1350 | 1351 | if (msg["more_page"]): 1352 | config_file_path = hass.config.path(f"dwains-dashboard/configs/more_pages/{msg['more_page']}/config.yaml") 1353 | 1354 | if os.path.exists(config_file_path) and os.stat(config_file_path).st_size != 0: 1355 | #with open(hass.config.path("dwains-dashboard/configs/more_pages/"+msg["more_page"]+"/config.yaml")) as f: 1356 | data = await hass.async_add_executor_job(open, config_file_path, "r") 1357 | with data as f: 1358 | configFile = yaml.safe_load(f) 1359 | else: 1360 | configFile = OrderedDict() 1361 | 1362 | configFile.update({ 1363 | "name": msg["name"], 1364 | "icon": msg["icon"], 1365 | "show_in_navbar": msg["showInNavbar"], 1366 | }) 1367 | 1368 | #with open(hass.config.path("dwains-dashboard/configs/more_pages/"+msg["more_page"]+"/config.yaml"), 'w') as f: 1369 | data = await hass.async_add_executor_job(open, config_file_path, "w") 1370 | with data as f: 1371 | yaml.dump(configFile, f, default_flow_style=False, sort_keys=False) 1372 | 1373 | # Trigger a reload event after saving 1374 | hass.bus.async_fire("dwains_dashboard_homepage_card_reload") 1375 | 1376 | # Send the response back to the connection 1377 | connection.send_result( 1378 | msg["id"], 1379 | { 1380 | "succesfull": "More page button saved" 1381 | }, 1382 | ) 1383 | 1384 | 1385 | #edit_more_page 1386 | @websocket_api.websocket_command( 1387 | { 1388 | vol.Required("type"): "dwains_dashboard/edit_more_page", 1389 | vol.Optional("card_data"): str, 1390 | vol.Optional("foldername"): str, 1391 | vol.Optional("name"): str, 1392 | vol.Optional("icon"): str, 1393 | vol.Optional("showInNavbar"): bool, 1394 | } 1395 | ) 1396 | @websocket_api.async_response 1397 | async def ws_handle_edit_more_page( 1398 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1399 | ) -> None: 1400 | """Handle edit more page command.""" 1401 | 1402 | if not msg["foldername"]: 1403 | more_page_folder = slugify(msg["name"]) 1404 | else: 1405 | more_page_folder = msg["foldername"] 1406 | 1407 | filecontent = json.loads(msg["card_data"]) 1408 | 1409 | path_to_more_page = hass.config.path("dwains-dashboard/configs/more_pages/"+more_page_folder+"/page.yaml") 1410 | 1411 | os.makedirs(os.path.dirname(path_to_more_page), exist_ok=True) # Create the folder if not exists 1412 | 1413 | if not msg["foldername"]: 1414 | if os.path.exists(path_to_more_page) and os.stat(path_to_more_page).st_size != 0: 1415 | more_page_folder = more_page_folder+datetime.now().strftime("%Y%m%d%H%M%S") 1416 | path_to_more_page = hass.config.path("dwains-dashboard/configs/more_pages/"+more_page_folder+"/page.yaml") 1417 | os.makedirs(os.path.dirname(path_to_more_page), exist_ok=True) 1418 | 1419 | 1420 | #ff = open(path_to_more_page, 'w+') 1421 | data = await hass.async_add_executor_job(open, path_to_more_page, "w+") 1422 | with data as ff: 1423 | yaml.dump(yaml.safe_load(json.dumps(filecontent)), ff, default_flow_style=False) 1424 | 1425 | # Prepare config.yaml content 1426 | configFile = OrderedDict() 1427 | configFile.update({ 1428 | "name": msg["name"], 1429 | "icon": msg["icon"], 1430 | "show_in_navbar": msg["showInNavbar"], 1431 | }) 1432 | 1433 | #with open(hass.config.path("dwains-dashboard/configs/more_pages/"+more_page_folder+"/config.yaml"), 'w') as f: 1434 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/more_pages/"+more_page_folder+"/config.yaml"), "w") 1435 | with data as f: 1436 | yaml.dump(configFile, f, default_flow_style=False, sort_keys=False) 1437 | #end config.yaml 1438 | 1439 | # Call reload config to rebuild the yaml for pages too 1440 | hass.bus.async_fire("dwains_dashboard_reload") 1441 | hass.bus.async_fire("dwains_dashboard_navigation_card_reload") 1442 | 1443 | #hass.services.call(DOMAIN, "reload") 1444 | 1445 | await reload_configuration(hass) 1446 | 1447 | connection.send_result( 1448 | msg["id"], 1449 | { 1450 | "succesfull": "More page saved succesfully" 1451 | }, 1452 | ) 1453 | 1454 | 1455 | #remove_more_page 1456 | @websocket_api.websocket_command( 1457 | { 1458 | vol.Required("type"): "dwains_dashboard/remove_more_page", 1459 | vol.Required("foldername"): str, 1460 | } 1461 | ) 1462 | @websocket_api.async_response 1463 | async def ws_handle_remove_more_page( 1464 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1465 | ) -> None: 1466 | """Handle remove more page command.""" 1467 | 1468 | path_to_more_page = hass.config.path("dwains-dashboard/configs/more_pages/"+msg["foldername"]+"/page.yaml") 1469 | #_LOGGER.warning(f"Removing more_page: {msg["foldername"]} -- {path_to_more_page}") 1470 | 1471 | #if os.path.exists(path_to_more_page): 1472 | if await hass.async_add_executor_job(os.path.exists, path_to_more_page): 1473 | #remove folder and content 1474 | #shutil.rmtree(hass.config.path("dwains-dashboard/configs/more_pages/"+msg["foldername"]), ignore_errors=True) 1475 | await hass.async_add_executor_job(shutil.rmtree, hass.config.path("dwains-dashboard/configs/more_pages/"+msg["foldername"]), True) 1476 | 1477 | hass.bus.async_fire("dwains_dashboard_navigation_card_reload") 1478 | 1479 | await reload_configuration(hass) 1480 | 1481 | hass.bus.async_fire("dwains_dashboard_reload") 1482 | 1483 | connection.send_result( 1484 | msg["id"], 1485 | { 1486 | "succesfull": "More page removed succesfully" 1487 | }, 1488 | ) 1489 | 1490 | 1491 | 1492 | #add_more_page_to_navbar 1493 | @websocket_api.websocket_command( 1494 | { 1495 | vol.Required("type"): "dwains_dashboard/add_more_page_to_navbar", 1496 | vol.Required("more_page"): str, 1497 | } 1498 | ) 1499 | @websocket_api.async_response 1500 | async def ws_handle_add_more_page_to_navbar( 1501 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1502 | ) -> None: 1503 | """Handle add more page to navbar command.""" 1504 | 1505 | #if os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+msg["more_page"]+"/config.yaml")): 1506 | # with open(hass.config.path("dwains-dashboard/configs/more_pages/"+msg["more_page"]+"/config.yaml")) as f: 1507 | # configFile = yaml.safe_load(f) 1508 | #else: 1509 | # configFile = OrderedDict() 1510 | # 1511 | # configFile.update({ 1512 | # "show_in_navbar": "True" 1513 | # }) 1514 | # 1515 | # with open(hass.config.path("dwains-dashboard/configs/more_pages/"+msg["more_page"]+"/config.yaml"), 'w') as f: 1516 | # yaml.safe_dump(configFile, f, default_flow_style=False) 1517 | 1518 | #call reload config to rebuild the yaml for pages too 1519 | hass.bus.async_fire("dwains_dashboard_reload") 1520 | hass.bus.async_fire("dwains_dashboard_navigation_card_reload") 1521 | 1522 | await reload_configuration(hass) 1523 | 1524 | connection.send_result( 1525 | msg["id"], 1526 | { 1527 | "succesfull": "More page removed succesfully" 1528 | }, 1529 | ) 1530 | 1531 | 1532 | #sort_area_button 1533 | @websocket_api.websocket_command( 1534 | { 1535 | vol.Required("type"): "dwains_dashboard/sort_area_button", 1536 | vol.Required("sortData"): str, 1537 | vol.Required("sortType"): str, 1538 | } 1539 | ) 1540 | @websocket_api.async_response 1541 | async def ws_handle_sort_area_button( 1542 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1543 | ) -> None: 1544 | """Handle sort area buttons command.""" 1545 | 1546 | sortData = json.loads(msg["sortData"]) 1547 | 1548 | sortType = msg["sortType"] 1549 | 1550 | if os.path.exists(hass.config.path("dwains-dashboard/configs/areas.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/areas.yaml")).st_size != 0: 1551 | #with open(hass.config.path("dwains-dashboard/configs/areas.yaml")) as f: 1552 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/areas.yaml"), "r") 1553 | with data as f: 1554 | areas = yaml.safe_load(f) 1555 | else: 1556 | areas = OrderedDict() 1557 | 1558 | for num, area_id in enumerate(sortData, start=1): 1559 | if areas.get(area_id): 1560 | areas[area_id].update({ 1561 | sortType: num, 1562 | }) 1563 | else: 1564 | areas[area_id] = OrderedDict({ 1565 | sortType: num, 1566 | }) 1567 | 1568 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 1569 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 1570 | 1571 | #with open(hass.config.path("dwains-dashboard/configs/areas.yaml"), 'w') as f: 1572 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/areas.yaml"), "w") 1573 | with data as f: 1574 | yaml.dump(areas, f, default_flow_style=False, sort_keys=False) 1575 | 1576 | connection.send_result( 1577 | msg["id"], 1578 | { 1579 | "succesfull": "Area buttons sorted succesfully" 1580 | }, 1581 | ) 1582 | 1583 | 1584 | 1585 | 1586 | #edit_device_bool_value 1587 | @websocket_api.websocket_command( 1588 | { 1589 | vol.Required("type"): "dwains_dashboard/edit_device_bool_value", 1590 | vol.Required("device"): str, 1591 | vol.Optional("key"): str, 1592 | vol.Optional("value"): bool, 1593 | } 1594 | ) 1595 | @websocket_api.async_response 1596 | async def ws_handle_edit_device_bool_value( 1597 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1598 | ) -> None: 1599 | """Handle edit device bool value command.""" 1600 | 1601 | if os.path.exists(hass.config.path("dwains-dashboard/configs/devices.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/devices.yaml")).st_size != 0: 1602 | #with open(hass.config.path("dwains-dashboard/configs/devices.yaml")) as f: 1603 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/devices.yaml"), "r") 1604 | with data as f: 1605 | devices = yaml.safe_load(f) 1606 | else: 1607 | devices = OrderedDict() 1608 | 1609 | entity = devices.get(msg["device"]) 1610 | 1611 | if not entity: 1612 | devices[msg["device"]] = OrderedDict() 1613 | 1614 | devices[msg["device"]].update({ 1615 | msg["key"]: msg["value"] 1616 | }) 1617 | 1618 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 1619 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 1620 | 1621 | #with open(hass.config.path("dwains-dashboard/configs/devices.yaml"), 'w') as f: 1622 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/devices.yaml"), "w") 1623 | with data as f: 1624 | yaml.dump(devices, f, default_flow_style=False, sort_keys=False) 1625 | 1626 | hass.bus.async_fire("dwains_dashboard_devicespage_card_reload") 1627 | 1628 | connection.send_result( 1629 | msg["id"], 1630 | { 1631 | "succesfull": "Device bool value set succesfully" 1632 | }, 1633 | ) 1634 | 1635 | 1636 | 1637 | #sort_device_button 1638 | @websocket_api.websocket_command( 1639 | { 1640 | vol.Required("type"): "dwains_dashboard/sort_device_button", 1641 | vol.Required("sortData"): str, 1642 | } 1643 | ) 1644 | @websocket_api.async_response 1645 | async def ws_handle_sort_device_button( 1646 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1647 | ) -> None: 1648 | """Handle sort device buttons command.""" 1649 | 1650 | sortData = json.loads(msg["sortData"]) 1651 | 1652 | if os.path.exists(hass.config.path("dwains-dashboard/configs/devices.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/devices.yaml")).st_size != 0: 1653 | #with open(hass.config.path("dwains-dashboard/configs/devices.yaml")) as f: 1654 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/devices.yaml"), "r") 1655 | with data as f: 1656 | devices = yaml.safe_load(f) 1657 | else: 1658 | devices = OrderedDict() 1659 | 1660 | for num, device_id in enumerate(sortData, start=1): 1661 | if devices.get(device_id): 1662 | devices[device_id].update({ 1663 | "sort_order": num, 1664 | }) 1665 | else: 1666 | devices[device_id] = OrderedDict({ 1667 | "sort_order": num, 1668 | }) 1669 | 1670 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 1671 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 1672 | 1673 | #with open(hass.config.path("dwains-dashboard/configs/devices.yaml"), 'w') as f: 1674 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/devices.yaml"), "w") 1675 | with data as f: 1676 | yaml.dump(devices, f, default_flow_style=False, sort_keys=False) 1677 | 1678 | connection.send_result( 1679 | msg["id"], 1680 | { 1681 | "succesfull": "Device buttons sorted succesfully" 1682 | }, 1683 | ) 1684 | 1685 | #sort_entity 1686 | @websocket_api.websocket_command( 1687 | { 1688 | vol.Required("type"): "dwains_dashboard/sort_entity", 1689 | vol.Required("sortData"): str, 1690 | vol.Required("sortType"): str, 1691 | } 1692 | ) 1693 | @websocket_api.async_response 1694 | async def ws_handle_sort_entity( 1695 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1696 | ) -> None: 1697 | """Handle sort entity cards.""" 1698 | 1699 | sortData = json.loads(msg["sortData"]) 1700 | 1701 | sortType = msg["sortType"] 1702 | 1703 | if os.path.exists(hass.config.path("dwains-dashboard/configs/entities.yaml")) and os.stat(hass.config.path("dwains-dashboard/configs/entities.yaml")).st_size != 0: 1704 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml")) as f: 1705 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/entities.yaml"), "r") 1706 | with data as f: 1707 | entities = yaml.safe_load(f) 1708 | else: 1709 | entities = OrderedDict() 1710 | 1711 | for num, entity_id in enumerate(sortData, start=1): 1712 | if entities.get(entity_id): 1713 | entities[entity_id].update({ 1714 | sortType: num, 1715 | }) 1716 | else: 1717 | entities[entity_id] = OrderedDict({ 1718 | sortType: num, 1719 | }) 1720 | 1721 | if not os.path.exists(hass.config.path("dwains-dashboard/configs")): 1722 | os.makedirs(hass.config.path("dwains-dashboard/configs")) 1723 | 1724 | #with open(hass.config.path("dwains-dashboard/configs/entities.yaml"), 'w') as f: 1725 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/entities.yaml"), "w") 1726 | with data as f: 1727 | yaml.dump(entities, f, default_flow_style=False, sort_keys=False) 1728 | 1729 | connection.send_result( 1730 | msg["id"], 1731 | { 1732 | "succesfull": "Entity cards sorted succesfully" 1733 | }, 1734 | ) 1735 | 1736 | 1737 | 1738 | #sort_more_page 1739 | @websocket_api.websocket_command( 1740 | { 1741 | vol.Required("type"): "dwains_dashboard/sort_more_page", 1742 | vol.Required("sortData"): str, 1743 | } 1744 | ) 1745 | @websocket_api.async_response 1746 | async def ws_handle_sort_more_page( 1747 | hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict 1748 | ) -> None: 1749 | """Handle sort more pages command.""" 1750 | 1751 | sortData = json.loads(msg["sortData"]) 1752 | 1753 | for num, more_page in enumerate(sortData, start=1): 1754 | page_link = hass.config.path("dwains-dashboard/configs/more_pages/"+more_page+"/config.yaml") 1755 | if os.path.exists(page_link) and os.stat(page_link).st_size != 0: 1756 | #with open(hass.config.path("dwains-dashboard/configs/more_pages/"+more_page+"/config.yaml")) as f: 1757 | data = await hass.async_add_executor_job(open, page_link, "r") 1758 | with data as f: 1759 | configFile = yaml.safe_load(f) 1760 | else: 1761 | configFile = OrderedDict() 1762 | 1763 | configFile.update({ 1764 | "sort_order": num, 1765 | }) 1766 | 1767 | #with open(hass.config.path("dwains-dashboard/configs/more_pages/"+more_page+"/config.yaml"), 'w') as f: 1768 | data = await hass.async_add_executor_job(open, page_link, "w") 1769 | with data as f: 1770 | yaml.dump(configFile, f, default_flow_style=False, sort_keys=False) 1771 | 1772 | connection.send_result( 1773 | msg["id"], 1774 | { 1775 | "succesfull": "More pages sorted succesfully" 1776 | }, 1777 | ) 1778 | 1779 | 1780 | async def async_setup_entry(hass, config_entry): 1781 | await process_yaml(hass, config_entry) 1782 | 1783 | load_dashboard(hass, config_entry) 1784 | 1785 | config_entry.add_update_listener(_update_listener) 1786 | 1787 | #hass.async_add_job( # Deprecated, trying with hass.async_create_task() ... 1788 | hass.async_create_task( 1789 | hass.config_entries.async_forward_entry_setups( 1790 | config_entry, ["sensor"] 1791 | ) 1792 | ) 1793 | 1794 | return True 1795 | 1796 | async def async_remove_entry(hass, config_entry): 1797 | _LOGGER.warning("Dwains Dashboard is now uninstalled.") 1798 | 1799 | frontend.async_remove_panel(hass, "dwains-dashboard") 1800 | 1801 | async def _update_listener(hass, config_entry): 1802 | _LOGGER.warning('Update_listener called') 1803 | 1804 | await process_yaml(hass, config_entry) 1805 | 1806 | hass.bus.async_fire("dwains_dashboard_reload") 1807 | 1808 | return True 1809 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/config_flow.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import voluptuous as vol 3 | 4 | from homeassistant import config_entries 5 | from homeassistant.core import callback 6 | 7 | _LOGGER = logging.getLogger(__name__) 8 | 9 | # Configuration: 10 | SIDEPANEL_TITLE = "sidepanel_title" 11 | SIDEPANEL_ICON = "sidepanel_icon" 12 | 13 | @config_entries.HANDLERS.register("dwains_dashboard") 14 | class DwainsDashboardConfigFlow(config_entries.ConfigFlow): 15 | async def async_step_user(self, user_input=None): 16 | if self._async_current_entries(): 17 | return self.async_abort(reason="single_instance_allowed") 18 | return self.async_create_entry(title="", data={}) 19 | 20 | @staticmethod 21 | @callback 22 | def async_get_options_flow(config_entry): 23 | return DwainsDashboardEditFlow(config_entry) 24 | 25 | class DwainsDashboardEditFlow(config_entries.OptionsFlow): 26 | def __init__(self, config_entry): 27 | self.config_entry = config_entry 28 | 29 | async def async_step_init(self, user_input=None): 30 | if user_input is not None: 31 | return self.async_create_entry(title="", data=user_input) 32 | 33 | schema = { 34 | vol.Optional(SIDEPANEL_TITLE, default=self.config_entry.options.get("sidepanel_title", "Dwains Dashboard")): str, 35 | vol.Optional(SIDEPANEL_ICON, default=self.config_entry.options.get("sidepanel_icon", "mdi:alpha-d-box")): str, 36 | } 37 | 38 | return self.async_show_form( 39 | step_id="init", 40 | data_schema=vol.Schema(schema) 41 | ) 42 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/const.py: -------------------------------------------------------------------------------- 1 | DOMAIN = "dwains_dashboard" 2 | VERSION = "3.8.0" -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/* 3 | !src/translations.js 4 | dwains-dashboard.js.map -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/js/dwains-dashboard.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! js-cookie v3.0.5 | MIT */ 2 | 3 | /** 4 | * @license 5 | * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. 6 | * This code may only be used under the BSD style license found at 7 | * http://polymer.github.io/LICENSE.txt 8 | * The complete set of authors may be found at 9 | * http://polymer.github.io/AUTHORS.txt 10 | * The complete set of contributors may be found at 11 | * http://polymer.github.io/CONTRIBUTORS.txt 12 | * Code distributed by Google as part of the polymer project is also 13 | * subject to an additional IP rights grant found at 14 | * http://polymer.github.io/PATENTS.txt 15 | */ 16 | 17 | /** 18 | * @license 19 | * Copyright 2017 Google LLC 20 | * SPDX-License-Identifier: BSD-3-Clause 21 | */ 22 | 23 | /** 24 | * @license 25 | * Copyright 2019 Google LLC 26 | * SPDX-License-Identifier: BSD-3-Clause 27 | */ 28 | 29 | /** 30 | @license 31 | Copyright (c) 2019 The Polymer Project Authors. All rights reserved. 32 | This code may only be used under the BSD style license found at 33 | http://polymer.github.io/LICENSE.txt The complete set of authors may be found at 34 | http://polymer.github.io/AUTHORS.txt The complete set of contributors may be 35 | found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as 36 | part of the polymer project is also subject to an additional IP rights grant 37 | found at http://polymer.github.io/PATENTS.txt 38 | */ 39 | 40 | /**! 41 | * Sortable 1.15.2 42 | * @author RubaXa 43 | * @author owenm 44 | * @license MIT 45 | */ 46 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dwains-dashboard", 3 | "private": true, 4 | "version": "3.8.0", 5 | "description": "dwains-dashboard", 6 | "scripts": { 7 | "build": "webpack --mode=production", 8 | "watch": "webpack --watch --mode=development", 9 | "update-card-tools": "npm uninstall card-tools && npm install thomasloven/lovelace-card-tools" 10 | }, 11 | "keywords": [], 12 | "author": "Dwain Scheeren", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "autoprefixer": "^10.2.5", 16 | "css-loader": "^5.1.3", 17 | "html-webpack-plugin": "^5.3.1", 18 | "postcss": "^8.2.8", 19 | "postcss-cli": "^8.3.1", 20 | "postcss-loader": "^5.2.0", 21 | "style-loader": "^2.0.0", 22 | "tailwindcss": "^2.0.3", 23 | "webpack": "^5.26.0", 24 | "webpack-cli": "^4.5.0", 25 | "webpack-dev-server": "^4.7.4", 26 | "webpack-merge": "^5.7.3" 27 | }, 28 | "dependencies": { 29 | "@mdi/js": "^6.5.95", 30 | "card-tools": "github:thomasloven/lovelace-card-tools", 31 | "custom-card-helpers": "^1.8.0", 32 | "js-cookie": "^3.0.1", 33 | "lit-element": "^2.2.1", 34 | "lit-html": "^1.1.2", 35 | "sortablejs": "^1.14.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/js/src/translations.js: -------------------------------------------------------------------------------- 1 | // Add additional languages with their ISO 639-1 language code 2 | 3 | const translations = { 4 | en: { 5 | global: { 6 | dwains_dashboard_settings: 'Dwains Dashboard Settings', 7 | enable_edit_mode: 'Enable edit mode', 8 | disable_edit_mode: 'Disable edit mode', 9 | version: 'Version', 10 | disable_clock: 'Disable clock', 11 | am_pm_clock: 'AM/PM clock', 12 | disable_welcome_message: 'Disable Welcome message', 13 | settings: 'Global settings', 14 | dashboard_information: 'Dashboard information', 15 | alarm_entity: 'Alarm entity', 16 | weather_entity: 'Weather entity', 17 | greeting_morning: 'Good morning', 18 | greeting_afternoon: 'Good afternoon', 19 | greeting_evening: 'Good evening', 20 | v2_mode: 'Enable Dwains Dashboard v2 mode (layout)', 21 | disable_sensor_graph: 'Disable show sensor as graph', 22 | invert_cover: 'Invert Cover', 23 | }, 24 | editor: { 25 | lovelace_card: 'Lovelace Card', 26 | create_lovelace_card: 'Create a new lovelace card from scratch', 27 | dwains_dashboard_blueprint: 'Dwains Dashboard Blueprint', 28 | use_dwains_dashboard_blueprint: 'Use a Dwain Dashboard Blueprint to create a card', 29 | row_span: 'Row span', 30 | row: 'Row', 31 | rows: 'Rows', 32 | col_span: 'Col span', 33 | column: 'Column', 34 | columns: 'Columns', 35 | default_col_row: 'Default col and row size', 36 | large_col_row: 'Large screen col and row size', 37 | extra_large_col_row: 'Extra large screen col and row size', 38 | }, 39 | entity: { 40 | title: 'Entity', 41 | title_plural: 'entities', 42 | add_card_to: 'Add card to ', 43 | edit_entity: 'Edit entity', 44 | edit_entity_card: 'Edit entity card', 45 | edit_entity_popup_card: 'Edit entity popup card', 46 | add_to_favorites: 'Add to favorites', 47 | remove_from_favorites: 'Remove from favorites', 48 | popup_card: 'Popup card', 49 | entity_card: 'Entity card', 50 | settings: 'Entity settings', 51 | group: 'Group by devices', 52 | ungroup: 'Ungroup by devices', 53 | enable: 'Enable entity', 54 | disable: 'Disable entity in DD', 55 | disable_all: 'Disable all entities', 56 | hide_all: 'Hide all entities', 57 | exclude: 'Exclude entity in DD', 58 | hide: 'Hide entity in DD', 59 | unhide: 'Unhide entity', 60 | use_popup_card: 'Use own popup card', 61 | use_entity_card: 'Use own entity card', 62 | friendly_name: 'Rename for DD', 63 | hidden: 'The following entities are hidden:', 64 | disabled: 'The following entities are disabled:', 65 | unavailable: 'The following entities are unavailable:', 66 | }, 67 | favorite: { 68 | title: 'Favorite', 69 | title_plural: 'Favorites', 70 | all_favorites: 'All favorites', 71 | }, 72 | home: { 73 | title: 'Home', 74 | }, 75 | area: { 76 | title: 'Area', 77 | title_plural: 'Areas', 78 | edit_area_button: 'Edit area button', 79 | group_by_floor: 'Group by floor', 80 | ungroup_by_floor: 'Ungroup by floor', 81 | icon: 'Area icon', 82 | floor: 'Area floor', 83 | no_floor: 'No floor', 84 | disable: 'Disable area in DD', 85 | disabled: 'The following areas are disabled:', 86 | enable: 'Enable area', 87 | }, 88 | device: { 89 | title: 'Device', 90 | title_plural: 'devices', 91 | edit_device_button: 'Edit device button', 92 | edit_device_card: 'Set custom entities card for domain ', 93 | edit_device_popup: 'Set custom entities popup for domain ', 94 | current_blueprint_card: 'You are currently using the following blueprint for all entities cards in the domain ', 95 | current_blueprint_popup: 'You are currently using the following blueprint for all entities popups in the domain ', 96 | icon_required: 'If you want to add it to navbar you must select an icon!', 97 | icon: 'Device icon', 98 | show_in_navbar: 'Add device page in main navbar', 99 | hide: 'Hide device overview', 100 | unhide: 'Unhide device overview', 101 | hidden: 'The following device overviews are hidden', 102 | see_all: 'See all', 103 | turn_all_off: 'Turn all off', 104 | on: 'on', 105 | open: 'open', 106 | closed: 'closed', 107 | cover: 'Cover', 108 | light: 'Light', 109 | climate: 'Climate', 110 | sensor: 'Sensor', 111 | binary_sensor: 'Binary Sensor', 112 | media_player: 'Media player', 113 | garage: 'Garage', 114 | shutter: 'Shutter', 115 | running: 'Running', 116 | remote: 'Remote', 117 | scene: 'Scene', 118 | number: 'Number', 119 | switch: 'Switch', 120 | button: 'Button', 121 | water_heater: 'Water heater', 122 | camera: 'Camera', 123 | select: 'Select', 124 | vacuum: 'Vacuum', 125 | fan: 'Fan', 126 | door: 'Door', 127 | window: 'Window', 128 | vibration: 'Vibration', 129 | motion: 'Motion', 130 | device_tracker: 'Device tracker', 131 | lock: 'Lock', 132 | input_boolean: 'Input boolean', 133 | weather: 'Weather', 134 | moisture: 'Moisture', 135 | input_select: 'Input select', 136 | carbon_monoxide: 'Carbon monoxide', 137 | gas: 'Gas', 138 | problem: 'Problem', 139 | safety: 'Safety', 140 | smoke: 'Smoke', 141 | tamper: 'Tamper', 142 | update: 'Update', 143 | person: 'Person', 144 | alarm_control_panel: 'Alarm control panel', 145 | automation: 'Automation', 146 | group: 'Group by areas', 147 | ungroup: 'Ungroup by areas', 148 | update: 'Update', 149 | script: 'Script', 150 | time: 'Time', 151 | event: 'Event', 152 | text: 'Text', 153 | }, 154 | more: { 155 | title: 'More', 156 | title_plural: 'More pages', 157 | pages: 'pages', 158 | create: 'Create new more page', 159 | edit: 'Edit more page', 160 | name_required: 'You must specify a name for the page', 161 | icon_required: 'If you want to add it to navbar you must select an icon!', 162 | add_navbar: 'Add this more page in main navbar', 163 | name: 'More page name', 164 | icon: 'More page icon', 165 | }, 166 | blueprint: { 167 | title: 'Blueprint', 168 | title_plural: 'Blueprints', 169 | yaml_required: 'No YAML code entered!', 170 | installed: 'Installed', 171 | no_blueprints_installed: 'No blueprints installed', 172 | not_installed: 'Not installed', 173 | installed_blueprints: 'Installed blueprints', 174 | type: 'Type blueprint', 175 | used_custom_cards: 'Used custom cards', 176 | use: 'Use this blueprint', 177 | install: 'Install blueprint', 178 | yaml_code: 'Blueprint YAML code', 179 | instruction: 'Look for the blueprint you want to install in the Dwains Dashboard Community Blueprints Github and paste the blueprint yaml code below. After succesfull installation lovelace and this page will reload. Then you can use the installed blueprint.', 180 | } 181 | }, 182 | 183 | //NL Dutch 184 | nl: { 185 | global: { 186 | dwains_dashboard_settings: 'Dwains Dashboard Instellingen', 187 | enable_edit_mode: 'Bewerkingsmodus inschakelen', 188 | disable_edit_mode: 'Bewerkingsmodus uitschakelen', 189 | version: 'Versie', 190 | disable_clock: 'Klok uitzetten', 191 | am_pm_clock: 'AM/PM klok', 192 | disable_welcome_message: 'Welkom bericht uitzetten', 193 | settings: 'Globale instellingen', 194 | dashboard_information: 'Dashboard informatie', 195 | alarm_entity: 'Alarm entiteit', 196 | weather_entity: 'Weer entiteit', 197 | greeting_morning: 'Goedemorgen', 198 | greeting_afternoon: 'Goedemiddag', 199 | greeting_evening: 'Goedenavond', 200 | v2_mode: 'Dwains Dashboard v2-modus inschakelen (lay-out)', 201 | disable_sensor_graph: 'Toon sensor als grafiek uitschakelen', 202 | }, 203 | editor: { 204 | lovelace_card: 'Lovelace Kaart', 205 | create_lovelace_card: 'Maak een nieuwe lovelace-kaart vanaf het begin', 206 | dwains_dashboard_blueprint: 'Dwains Dashboard Blueprint', 207 | use_dwains_dashboard_blueprint: 'Een Dwain Dashboard Blueprint gebruiken om een kaart te maken', 208 | row_span: 'Rij span', 209 | row: 'Rij', 210 | rows: 'Rijen', 211 | col_span: 'Kolom span', 212 | column: 'Kolom', 213 | columns: 'Kolommen', 214 | default_col_row: 'Standaard col en rijgrootte', 215 | large_col_row: 'Groot scherm kleur en rijgrootte', 216 | extra_large_col_row: 'Extra grote schermkleur en rijgrootte', 217 | }, 218 | entity: { 219 | title: 'Entiteit', 220 | title_plural: 'entiteiten', 221 | add_card_to: 'Kaart toevoegen aan ', 222 | edit_entity: 'Bewerk entiteit', 223 | edit_entity_card: 'Bewerk entiteit kaart', 224 | edit_entity_popup_card: 'Bewerk entiteit popup kaart', 225 | add_to_favorites: 'Toevoegen aan favorieten', 226 | remove_from_favorites: 'Verwijderen van favorieten', 227 | popup_card: 'Popup kaart', 228 | entity_card: 'Entiteit kaart', 229 | settings: 'Entiteit instellingen', 230 | group: 'Groep entiteiten', 231 | ungroup: 'Groep entiteiten opheffen', 232 | enable: 'Entiteit aanzetten', 233 | disable: 'Entiteit uitschakelen in DD', 234 | disable_all: 'Alle entiteiten uitschakelen', 235 | hide_all: 'Alle entiteiten verbergen', 236 | exclude: 'Entiteit uitsluiten in DD', 237 | hide: 'Verberg entiteit in DD', 238 | unhide: 'Entiteit zichtbaar maken', 239 | use_popup_card: 'Eigen pop-upkaart gebruiken', 240 | use_entity_card: 'Eigen entiteitskaart gebruiken', 241 | friendly_name: 'Naam wijzigen voor DD', 242 | hidden: 'De volgende entiteiten zijn verborgen:', 243 | disabled: 'De volgende entiteiten zijn uitgeschakeld:', 244 | unavailable: 'De volgende entiteiten zijn niet beschikbaar:', 245 | }, 246 | favorite: { 247 | title: 'Favoriet', 248 | title_plural: 'Favorieten', 249 | all_favorites: 'Alle favorieten', 250 | }, 251 | home: { 252 | title: 'Home', 253 | }, 254 | area: { 255 | title: 'Gebied', 256 | title_plural: 'Gebieden', 257 | edit_area_button: 'Bewerk gebied knop', 258 | group_by_floor: 'Groeperen op verdieping', 259 | ungroup_by_floor: 'Groepering opheffen op verdieping', 260 | icon: 'Gebied icoon', 261 | floor: 'Gebied verdieping', 262 | no_floor: 'Geen verdieping', 263 | disable: 'Schakel gebied uit in DD', 264 | disabled: 'De volgende gebieden zijn uitgeschakeld:', 265 | enable: 'Gebied inschakelen', 266 | }, 267 | device: { 268 | title: 'Apparaat', 269 | title_plural: 'apparaten', 270 | edit_device_button: 'Apparaatknop bewerken', 271 | edit_device_card: 'Stel een custom entititeit kaart in voor domein ', 272 | edit_device_popup: 'Stel een custom popup entiteiten in voor domein ', 273 | current_blueprint_card: 'U gebruikt momenteel de volgende blueprint voor alle entiteitskaarten in het domein: ', 274 | current_blueprint_popup: 'U gebruikt momenteel de volgende blueprint voor alle pop-ups van entiteiten in het domein: ', 275 | icon_required: 'Als u het aan de navigatiebalk wilt toevoegen, moet u een icon selecteren!', 276 | icon: 'Apparaat icon', 277 | show_in_navbar: 'Apparaatpagina toevoegen in hoofdnavigatiebalk', 278 | hide: 'Apparaatoverzicht verbergen', 279 | unhide: 'Apparaatoverzicht zichtbaar maken', 280 | hidden: 'De volgende apparaatoverzichten zijn verborgen', 281 | see_all: 'Bekijk alle', 282 | turn_all_off: 'Zet alle uit', 283 | on: 'aan', 284 | open: 'open', 285 | cover: 'Rolluik', 286 | light: 'Lamp', 287 | climate: 'Thermostaat', 288 | sensor: 'Sensor', 289 | binary_sensor: 'Binaire sensor', 290 | media_player: 'Media player', 291 | remote: 'Afstandsbediening', 292 | scene: 'Scène', 293 | number: 'Nummer', 294 | switch: 'Schakelaar', 295 | button: 'Knop', 296 | water_heater: 'Water verwarmer', 297 | camera: 'Camera', 298 | select: 'Selecteer', 299 | vacuum: 'Stofzuiger', 300 | fan: 'Ventilator', 301 | door: 'Deur', 302 | window: 'Raam', 303 | vibration: 'Vibratie', 304 | motion: 'Beweging', 305 | device_tracker: 'Device tracker', 306 | lock: 'Slot', 307 | input_boolean: 'Input boolean', 308 | weather: 'Weer', 309 | moisture: 'Vochtigheid', 310 | input_select: 'Input select', 311 | carbon_monoxide: 'Koolmonoxide', 312 | gas: 'Gas', 313 | problem: 'Probleem', 314 | safety: 'Veiligheid', 315 | smoke: 'Rook', 316 | tamper: 'Geknoeid', 317 | update: 'Update', 318 | person: 'Persoon', 319 | alarm_control_panel: 'Alarm bedieningspaneel', 320 | automation: 'Automatisering', 321 | group: 'Groeperen op gebied', 322 | ungroup: 'Groepering opheffen op gebied', 323 | }, 324 | more: { 325 | title: 'Meer', 326 | title_plural: 'Meer pagina\'s', 327 | pages: 'pagina\'s', 328 | create: 'Nieuwe meer pagina maken', 329 | edit: 'Meer pagina bewerken', 330 | name_required: 'U moet een naam voor de pagina opgeven', 331 | icon_required: 'Als u het aan de navigatiebalk wilt toevoegen, moet u een icoon selecteren!', 332 | add_navbar: 'Voeg deze meer pagina toe in de hoofdnavigatiebalk', 333 | name: 'Meer pagina naam', 334 | icon: 'Meer pagina icoon', 335 | }, 336 | blueprint: { 337 | title: 'Blueprint', 338 | title_plural: 'Blueprints', 339 | yaml_required: 'Geen YAML-code ingevoerd!', 340 | installed: 'Geïnstalleerd', 341 | no_blueprints_installed: 'Geen blueprints geïnstalleerd', 342 | not_installed: 'Niet geïnstalleerd', 343 | installed_blueprints: 'Installeer blueprints', 344 | type: 'Type blueprint', 345 | used_custom_cards: 'Gebruikte custom cards', 346 | use: 'Gebruik deze blueprint', 347 | install: 'Installeer blueprint', 348 | yaml_code: 'Blueprint YAML-code', 349 | instruction: 'Zoek de blueprint die u wilt installeren in de Dwains Dashboard Community Blueprints Github en plak de blueprint yaml-code hieronder. Na succesvolle installatie worden lovelace en deze pagina opnieuw geladen. Dan kunt u de geïnstalleerde blueprint gebruiken.', 350 | } 351 | }, 352 | 353 | //FR French 354 | fr: { 355 | global: { 356 | dwains_dashboard_settings: 'Paramètres Dwains Dashboard', 357 | enable_edit_mode: 'Activer le mode d\'édition', 358 | disable_edit_mode: 'Désactiver le mode d\'édition', 359 | version: 'Version', 360 | disable_clock: 'Masquer l\'horloge', 361 | am_pm_clock: 'AM/PM l\'horloge', 362 | disable_welcome_message: 'Masquer le message de bienvenue', 363 | settings: 'Paramètres globaux', 364 | dashboard_information: 'Informations', 365 | alarm_entity: 'Entité pour l\'Alarme', 366 | weather_entity: 'Entité pour la Météo', 367 | greeting_morning: 'Bonjour', 368 | greeting_afternoon: 'Bonne après-midi', 369 | greeting_evening: 'Bonsoir', 370 | v2_mode: 'Activer le mode Dwains Dashboard v2 (mise en page)', 371 | disable_sensor_graph: 'Désactiver l\'affichage du capteur sous forme de graphique', 372 | }, 373 | editor: { 374 | lovelace_card: 'Carte Lovelace', 375 | create_lovelace_card: 'Créer une nouvelle carte Lovelace', 376 | dwains_dashboard_blueprint: 'Dwains Dashboard Blueprint', 377 | use_dwains_dashboard_blueprint: 'Utiliser un Blueprint pour créer une carte', 378 | row_span: 'Nombre de rangée', 379 | row: 'Rangée', 380 | rows: 'Rangées', 381 | col_span: 'Nombre de colonne', 382 | column: 'Colonne', 383 | columns: 'Colonnes', 384 | default_col_row: 'Default : nombre de colonne et rangée', 385 | large_col_row: 'Grand écran : nombre de colonne et rangée', 386 | extra_large_col_row: 'Très grande écran : nombre de colonne et rangée', 387 | }, 388 | entity: { 389 | title: 'Entité', 390 | title_plural: 'Entités', 391 | add_card_to: 'Ajouter la carte à ', 392 | edit_entity: 'Éditer l\'entité', 393 | edit_entity_card: 'Éditer une carte d\'entité', 394 | edit_entity_popup_card: 'Éditer une carte Pop-up', 395 | add_to_favorites: 'Ajouter aux favoris', 396 | remove_from_favorites: 'Retirer des favoris', 397 | popup_card: 'Carte Pop-up', 398 | entity_card: 'Carte de l\'entité', 399 | settings: 'Paramètres de l\'entité', 400 | group: 'Regrouper par entités', 401 | ungroup: 'Dégrouper par entités', 402 | enable: 'Activer l\'entité', 403 | disable: 'Désactiver l\'entité dans DD', 404 | disable_all: 'Désactiver toutes les entités', 405 | hide_all: 'Masquer toutes les entités', 406 | exclude: 'Exclure l\'entité dans DD', 407 | hide: 'Masquer l\'entité dans DD', 408 | unhide: 'Afficher l\'entité', 409 | use_popup_card: 'Utiliser une carte Pop-up', 410 | use_entity_card: 'Utiliser une carte d\'entité', 411 | friendly_name: 'Renommer dans DD', 412 | hidden: 'Les entités suivantes sont masqués:', 413 | disabled: 'Les entités suivantes sont désactivés:', 414 | unavailable: 'Les entités suivantes sont indisponibles:', 415 | }, 416 | favorite: { 417 | title: 'Favori', 418 | title_plural: 'Favoris', 419 | all_favorites: 'Tous les Favoris', 420 | }, 421 | home: { 422 | title: 'Accueil', 423 | }, 424 | area: { 425 | title: 'Pièce', 426 | title_plural: 'Pièces', 427 | edit_area_button: 'Édition d\'une Pièce', 428 | group_by_floor: 'Regrouper par étage', 429 | ungroup_by_floor: 'Dégrouper par étage', 430 | icon: 'Icône', 431 | floor: 'Étage', 432 | no_floor: 'Aucun n\'étage', 433 | disable: 'Désactiver l\'aréa dans DD', 434 | disabled: 'Les aréas suivantes sont désactivées:', 435 | enable: 'Activer l\'aréa', 436 | }, 437 | device: { 438 | title: 'Appareil', 439 | title_plural: 'Appareils', 440 | edit_device_button: 'Édition d\'un appareil', 441 | edit_device_card: 'Définir une carte d\'entités personnalisées pour le domaine ', 442 | edit_device_popup: 'Définir une carte Pop-up d\'entités personnalisées pour le domaine ', 443 | current_blueprint_card: 'Vous utilisez actuellement le Blueprint suivant pour toutes les cartes d\'entités du domaine ', 444 | current_blueprint_popup: 'Vous utilisez actuellement le Blueprint suivant pour tout les Pop-up d\'entités du domaine ', 445 | icon_required: 'Si vous voulez l\'ajouter à la barre de navigation, vous devez choisir une icône!', 446 | icon: 'icône de l\'appareil', 447 | show_in_navbar: 'Ajouter la page d\'appareils à la barre de navigation', 448 | hide: 'Masquer l\'aperçu de l\'appareil', 449 | unhide: 'Afficher l\'aperçu de l\'appareil', 450 | see_all: 'Voir tout', 451 | turn_all_off: 'Tout désactiver', 452 | on: 'Allumé', 453 | open: 'Ouvert', 454 | cover: 'Rideau', 455 | light: 'Lumière', 456 | climate: 'Thermostat', 457 | sensor: 'Capteur', 458 | binary_sensor: 'Binaire', 459 | media_player: 'Multimédia', 460 | remote: 'Télécommande', 461 | scene: 'Scène', 462 | number: 'Nombre', 463 | switch: 'Interrupteur', 464 | button: 'Bouton', 465 | water_heater: 'Chauffe-eau', 466 | camera: 'Caméra', 467 | select: 'Sélection', 468 | vacuum: 'Aspirateur', 469 | fan: 'Ventilateur', 470 | door: 'Porte', 471 | window: 'Fenêtre', 472 | vibration: 'Vibration', 473 | motion: 'Mouvement', 474 | device_tracker: 'Traqueur', 475 | lock: 'Serrure', 476 | input_boolean: 'Booléen', 477 | weather: 'Temps', 478 | moisture: 'Humidité', 479 | input_select: 'Sélection', 480 | carbon_monoxide: 'Monoxyde de carbone', 481 | gas: 'Gaz', 482 | problem: 'Problème', 483 | safety: 'Sécurité', 484 | smoke: 'Fumée', 485 | tamper: 'Altérer', 486 | update: 'Mise à jour', 487 | person: 'Personne', 488 | alarm_control_panel: 'Alarme', 489 | automation: 'Automatisation', 490 | group: 'Regrouper par aréas', 491 | ungroup: 'Dégrouper par aréas', 492 | }, 493 | more: { 494 | title: 'Ajouter', 495 | title_plural: 'Ajouter une page', 496 | pages: 'Pages', 497 | create: 'Créer une nouvelle page', 498 | edit: 'Éditer une page', 499 | name_required: 'Vous devez indiquer le nom de la page', 500 | icon_required: 'Si vous voulez l\'ajouter à la barre de navigation, vous devez choisir une icône!', 501 | add_navbar: 'Ajouter à la barre de navigation', 502 | name: 'Nom de la page', 503 | icon: 'icône de la page', 504 | }, 505 | blueprint: { 506 | title: 'Blueprint', 507 | title_plural: 'Blueprints', 508 | yaml_required: 'Pas de code YAML entré!', 509 | installed: 'Installé', 510 | no_blueprints_installed: 'Pas de Blueprints installés', 511 | not_installed: 'N\'est pas installé', 512 | installed_blueprints: 'Blueprints installés', 513 | type: 'Type de Blueprint', 514 | used_custom_cards: 'Cartes personnalisées installées', 515 | use: 'Ajouter', 516 | install: 'Installer un Blueprint', 517 | yaml_code: 'Code YAML du Blueprint', 518 | instruction: 'Trouver le code du Blueprint que vous voulez installer dans le GitHub de la communauté de Dwains et collé le dans la section Code YAML du Blueprint plus basse. Après l’installation réussie, le tableau de bord va se recharger. Alors, vous pourrez utiliser le Blueprint.', 519 | } 520 | }, 521 | 522 | //DE German 523 | de: { 524 | global: { 525 | dwains_dashboard_settings: 'Dwains Dashboard Einstellungen', 526 | enable_edit_mode: 'Aktiviere Bearbeitungsmodus', 527 | disable_edit_mode: 'Deaktiviere Bearbeitungsmodus', 528 | version: 'Version', 529 | disable_clock: 'Deaktiviere Uhr', 530 | am_pm_clock: 'AM/PM Uhr', 531 | disable_welcome_message: 'Deaktiviere Willkommensnachricht', 532 | settings: 'Globale Einstellungen', 533 | dashboard_information: 'Dashboard Informationen', 534 | alarm_entity: 'Alarm Entität', 535 | weather_entity: 'Wetter Entität', 536 | greeting_morning: 'Guten Morgen', 537 | greeting_afternoon: 'Guten Tag', 538 | greeting_evening: 'Guten Abend', 539 | v2_mode: 'Aktiviere Dwains Dashboard v2 Layout', 540 | disable_sensor_graph: 'Sensordarstellung als Grafik deaktivieren', 541 | }, 542 | editor: { 543 | lovelace_card: 'Lovelace Karte', 544 | create_lovelace_card: 'Erstelle eine neue Lovelace Karte', 545 | dwains_dashboard_blueprint: 'Dwains Dashboard Blueprint', 546 | use_dwains_dashboard_blueprint: 'Nutze Dwains Dashboard Blueprint um eine Karte zu erstellen', 547 | row_span: 'Anzahl Zeilen', 548 | row: 'Zeile', 549 | rows: 'Zeilen', 550 | col_span: 'Anzahl Spalten', 551 | column: 'Spalte', 552 | columns: 'Spalten', 553 | default_col_row: 'Standardgröße von Zeilen und Spalten', 554 | large_col_row: 'Zeilen und Spaltengröße für große Bildschirme', 555 | extra_large_col_row: 'Zeilen und Spaltengröße für extra große Bildschirme', 556 | }, 557 | entity: { 558 | title: 'Entität', 559 | title_plural: 'Entitäten', 560 | add_card_to: 'Füge Karte hinzu ', 561 | edit_entity: 'Bearbeite Entität', 562 | edit_entity_card: 'Bearbeite Entität-Karte', 563 | edit_entity_popup_card: 'Bearbeite Pop-up-Karte', 564 | add_to_favorites: 'Zu Favoriten hinzufügen', 565 | remove_from_favorites: 'Entferne von Favoriten', 566 | popup_card: 'Pop-up Karte', 567 | entity_card: 'Enität-Karte', 568 | settings: 'Entität Einstellungen', 569 | group: 'Gruppierung nach Entitäten', 570 | ungroup: 'Gruppierung nach Entitäten aufheben', 571 | enable: 'Aktiviere Entität', 572 | disable: 'Deaktiviere Entität in DD', 573 | disable_all: 'Deaktiviere alle Entitäten', 574 | hide_all: 'Blende alle Entitäten aus', 575 | exclude: 'Schließe Entität in DD aus', 576 | hide: 'Blende Entität in DD aus', 577 | unhide: 'Blende Entität in DD ein', 578 | use_popup_card: 'Nutze eigene Pop-up Karte', 579 | use_entity_card: 'Nutze eigene Entität-Karte', 580 | friendly_name: 'Nenne in DD um', 581 | hidden: 'Folgende Entitäten sind ausgeblendet:', 582 | disabled: 'Folgende Entitäten sind deaktiviert:', 583 | unavailable: 'Folgende Entitäten sind nicht verfügbar:', 584 | }, 585 | favorite: { 586 | title: 'Favorit', 587 | title_plural: 'Favoriten', 588 | all_favorites: 'Alle Favoriten', 589 | }, 590 | home: { 591 | title: 'Home', 592 | }, 593 | area: { 594 | title: 'Bereich', 595 | title_plural: 'Bereiche', 596 | edit_area_button: 'Schaltfläche Bearbeite Bereiche', 597 | group_by_floor: 'Gruppierung nach Etage', 598 | ungroup_by_floor: 'Gruppierung nach Etage aufheben', 599 | icon: 'Icon des Bereichs', 600 | floor: 'Etage', 601 | no_floor: 'Keine Etagen', 602 | disable: 'Deaktiviere Bereich in DD', 603 | disabled: 'Folgende Bereiche sind deaktiviert:', 604 | enable: 'Aktiviere Bereich', 605 | }, 606 | device: { 607 | title: 'Gerät', 608 | title_plural: 'Geräte', 609 | edit_device_button: 'Schaltfläche Geräte bearbeiten', 610 | edit_device_card: 'Nutze benutzerdefinierte Entität-Karte für Domäne ', 611 | edit_device_popup: 'Nutze benutzerdefinierte Pop-Up-Karte der Domäne ', 612 | current_blueprint_card: 'Derzeitige Nutzung des Blueprints für alle Entität-Karten der Domäne ', 613 | current_blueprint_popup: 'Derzeitige Nutzung des Blueprints für alle Entität-Pop-Up-Karten der Domäne ', 614 | icon_required: 'Es muss ein Icon gewählt werden, um dies der Navigationsleiste hinzuzufügen!', 615 | icon: 'Icon des Geräts', 616 | show_in_navbar: 'Füge Geräte Seite der Navigationsleiste hinzu.', 617 | hide: 'Blende Geräteübersicht aus', 618 | unhide: 'Blende Geräteübersicht ein', 619 | hidden: 'Folgende Geräteübersichten sind ausgeblendet', 620 | see_all: 'Zeige Alle', 621 | turn_all_off: 'Schalte alle aus', 622 | on: 'an', 623 | open: 'offen', 624 | cover: 'Beschattung', 625 | light: 'Licht', 626 | climate: 'Klima', 627 | sensor: 'Sensor', 628 | binary_sensor: 'Binärer Sensor', 629 | media_player: 'Medien', 630 | remote: 'Fernbedienung', 631 | scene: 'Szene', 632 | number: 'Nummer', 633 | switch: 'Schalter', 634 | button: 'Schaltfläche', 635 | water_heater: 'Warmwassererzeuger', 636 | camera: 'Kamera', 637 | select: 'Auswahl', 638 | vacuum: 'Staubsauger', 639 | fan: 'Lüfter', 640 | door: 'Tür', 641 | window: 'Fenster', 642 | vibration: 'Vibration', 643 | motion: 'Bewegung', 644 | device_tracker: 'Geräte-Tracker', 645 | lock: 'Schloss', 646 | input_boolean: 'Input Boolean', 647 | weather: 'Wetter', 648 | moisture: 'Feuchtigkeit', 649 | input_select: 'Input Select', 650 | carbon_monoxide: 'Kohlenstoffmonoxid', 651 | gas: 'Gas', 652 | problem: 'Problem', 653 | safety: 'Sicherheit', 654 | smoke: 'Rauch', 655 | tamper: 'Manipulation', 656 | update: 'Update', 657 | person: 'Person', 658 | alarm_control_panel: 'Alarmanlage', 659 | automation: 'Automation', 660 | group: 'Gruppierung nach Bereichen', 661 | ungroup: 'Gruppierung nach Bereichen aufheben', 662 | }, 663 | more: { 664 | title: 'Weitere', 665 | title_plural: 'Weitere Seiten', 666 | pages: 'Seiten', 667 | create: 'Erstelle neue Weitere Seite', 668 | edit: 'Bearbeite Weitere Seite', 669 | name_required: 'Wähle einen Namen für die Seite', 670 | icon_required: 'Es muss ein Icon gewählt werden, um dies der Navigationsleiste hinzuzufügen!', 671 | add_navbar: 'Füge die Weitere Seite zu Navigationsleiste hinzu', 672 | name: 'Name der Weiteren Seite', 673 | icon: 'Icon der Weiteren Seite', 674 | }, 675 | blueprint: { 676 | title: 'Blueprint', 677 | title_plural: 'Blueprints', 678 | yaml_required: 'Kein YAML Code eingegeben!', 679 | installed: 'Installiert', 680 | no_blueprints_installed: 'Keine Blueprints installiert', 681 | not_installed: 'Nicht installiert', 682 | installed_blueprints: 'Installierte Blueprints', 683 | type: 'Typ des Blueprints', 684 | used_custom_cards: 'Verwendete benutzerdefinierte Karten', 685 | use: 'Verwende diesen Blueprint', 686 | install: 'Installiere Blueprint', 687 | yaml_code: 'Blueprint YAML Code', 688 | instruction: 'Suche im Dwains Dashboard Community Blueprints Github nach dem Blueprint die installiert werden soll und füge den YAML Code unten ein. Nach erfolgreicher Installation wird die Seite neugeladen. Danach kann der Blueprint genutzt werden.', 689 | }, 690 | }, 691 | 692 | //PT PORTUGUESE 693 | pt: { 694 | global: { 695 | dwains_dashboard_settings: 'Configurações do painel Dwains', 696 | enable_edit_mode: 'Ativar o modo de edição', 697 | disable_edit_mode: 'Desabilitar o modo de edição', 698 | version: 'Versão', 699 | disable_clock: 'Desativar relógio', 700 | am_pm_clock: 'AM/PM clock', 701 | disable_welcome_message: 'Desativar mensagem de boas vindas', 702 | settings: 'Configurações globais', 703 | dashboard_information: 'Informações do painel', 704 | alarm_entity: 'Entidade de alarme', 705 | weather_entity: 'Entidade meteorológica', 706 | greeting_morning: 'Bom dia', 707 | greeting_afternoon: 'Boa tarde', 708 | greeting_evening: 'Boa noite', 709 | v2_mode: 'Enable Dwains Dashboard v2 mode (layout)', 710 | disable_sensor_graph: 'Desativar a exibição do sensor como gráfico', 711 | }, 712 | editor: { 713 | lovelace_card: 'Cartão Lovelace', 714 | create_lovelace_card: 'Crie um novo cartão lovelace do zero', 715 | dwains_dashboard_blueprint: 'Planta do painel Dwains ', 716 | use_dwains_dashboard_blueprint: 'Use uma planta do painel Dwains para criar um cartão', 717 | row_span: 'Expansão de linha', 718 | row: 'Linha', 719 | rows: 'Linhas', 720 | col_span: 'Extensão da coluna', 721 | column: 'Coluna', 722 | columns: 'Colunas', 723 | default_col_row: 'Tamanho padrão de coluna e linha', 724 | large_col_row: 'Tamanho de coluna e linha de tela grande', 725 | extra_large_col_row: 'Tamanho de coluna e linha de tela extra grande', 726 | }, 727 | entity: { 728 | title: 'Entidade', 729 | title_plural: 'Entidades', 730 | add_card_to: 'Adicionar cartão a ', 731 | edit_entity: 'Editar entidade', 732 | edit_entity_card: 'Editar cartão de entidade', 733 | edit_entity_popup_card: 'Editar cartão pop-up de entidade', 734 | add_to_favorites: 'Adicionar aos favoritos', 735 | remove_from_favorites: 'Remover dos favoritos', 736 | popup_card: 'Cartão pop-up', 737 | entity_card: 'Cartão de entidade', 738 | settings: 'Configurações da entidade', 739 | group: 'Agrupar por dispositivos', 740 | ungroup: 'Desagrupar por dispositivos', 741 | enable: 'Ativar entidade', 742 | disable: 'Desativar entidade no DD', 743 | disable_all: 'Disable all entities', 744 | hide_all: 'Hide all entities', 745 | exclude: 'Exclude entity in DD', 746 | hide: 'Ocultar entidade no DD', 747 | unhide: 'Reexibir entidade', 748 | use_popup_card: 'Use o próprio cartão pop-up', 749 | use_entity_card: 'Use o próprio cartão de entidade', 750 | friendly_name: 'Renomear para DD', 751 | hidden: 'As seguintes entidades estão ocultas:', 752 | disabled: 'As seguintes entidades estão desabilitadas:', 753 | unavailable: 'As seguintes entidades estão indisponíveis:', 754 | }, 755 | favorite: { 756 | title: 'Favorito', 757 | title_plural: 'Favoritos', 758 | all_favorites: 'Todos os favoritos', 759 | }, 760 | home: { 761 | title: 'Inicio', 762 | }, 763 | area: { 764 | title: 'Divisão', 765 | title_plural: 'Divisões', 766 | edit_area_button: 'Botão Editar divisão', 767 | group_by_floor: 'Agrupar por andar', 768 | ungroup_by_floor: 'Desagrupar por andar', 769 | icon: 'Ícone da divisão', 770 | floor: 'Piso da divisão', 771 | no_floor: 'Sem piso', 772 | disable: 'Disable area in DD', 773 | disabled: 'The following areas are disabled:', 774 | enable: 'Enable area', 775 | }, 776 | device: { 777 | title: 'Dispositivo', 778 | title_plural: 'Dispositivos', 779 | edit_device_button: 'Botão Editar dispositivo', 780 | edit_device_card: 'Definir cartão de entidades personalizadas para domínio ', 781 | edit_device_popup: 'Definir pop-up de entidades personalizadas para o domínio', 782 | current_blueprint_card: 'Você está usando o seguinte esquema para todos os cartões de entidades no domínio', 783 | current_blueprint_popup: 'Você está usando o seguinte esquema para todos os pop-ups de entidades no domínio', 784 | icon_required: 'Se você quiser adicioná-lo à barra de navegação, você deve selecionar um ícone!', 785 | icon: 'Ícone do dispositivo', 786 | show_in_navbar: 'Adicionar página do dispositivo na barra de navegação principal', 787 | hide: 'Hide device overview', 788 | unhide: 'Unhide device overview', 789 | hidden: 'The following device overviews are hidden', 790 | see_all: 'Ver tudo', 791 | turn_all_off: 'Turn all off', 792 | on: 'ligado', 793 | open: 'aberto', 794 | cover: 'Persiana', 795 | light: 'Luz', 796 | climate: 'Clima', 797 | sensor: 'Sensor', 798 | binary_sensor: 'Sensor binário', 799 | media_player: 'Reprodutor de mídia', 800 | remote: 'Controlo remoto', 801 | scene: 'Cena', 802 | number: 'Número', 803 | switch: 'Interruptor', 804 | button: 'Botão', 805 | water_heater: 'Aquecedor de água', 806 | camera: 'Camera', 807 | select: 'Select', 808 | vacuum: 'Aspirador', 809 | fan: 'Ventoinha', 810 | door: 'Porta', 811 | window: 'Janela', 812 | vibration: 'Vibração', 813 | motion: 'Movimento', 814 | device_tracker: 'Rastreador de dispositivo', 815 | lock: 'Fechadura', 816 | input_boolean: 'Booleano de entrada', 817 | weather: 'Clima', 818 | moisture: 'Umidade', 819 | input_select: 'Seleção de entrada', 820 | carbon_monoxide: 'Monóxido de carbono', 821 | gas: 'Gás', 822 | problem: 'Problema', 823 | safety: 'Segurança', 824 | smoke: 'Fumo', 825 | tamper: 'Adulterar', 826 | update: 'Update', 827 | }, 828 | more: { 829 | title: 'Mais', 830 | title_plural: 'Páginas adicionais', 831 | pages: 'Páginas', 832 | create: 'Criar mais uma página', 833 | edit: 'Editar página adicional', 834 | name_required: 'Você deve especificar um nome para a página', 835 | icon_required: 'Se você quiser adicioná-lo à barra de navegação, você deve selecionar um ícone!', 836 | add_navbar: 'Adicione mais esta página na barra de navegação principal', 837 | name: 'Nome da página adicional', 838 | icon: 'Ícone da página adicional', 839 | }, 840 | blueprint: { 841 | title: 'Esquema', 842 | title_plural: 'Esquemas', 843 | yaml_required: 'Nenhum código YAML inserido!', 844 | installed: 'Instalado', 845 | no_blueprints_installed: 'Nenhum esquema instalada', 846 | not_installed: 'Não instalado', 847 | installed_blueprints: 'Esquemas instalados', 848 | type: 'Tipo de esquema', 849 | used_custom_cards: 'Cartões personalizados usados', 850 | use: 'Use este esquema', 851 | install: 'Instalar esquema', 852 | yaml_code: 'Código YAML do esquema', 853 | instruction: 'Procure o esquema que deseja instalar em Dwains Dashboard Community Blueprints Github e cole o código yaml do mesmo abaixo. Após a instalação bem sucedida, o lovelace e esta página serão recarregadas. Então você pode usar o esquema instalado.', 854 | } 855 | }, 856 | 857 | //SV Swedish 858 | sv: { 859 | global: { 860 | dwains_dashboard_settings: 'Dwains Dashboardinställningar', 861 | enable_edit_mode: 'Aktivera redigeringsläge', 862 | disable_edit_mode: 'Inaktivera redigeringsläge', 863 | version: 'Version', 864 | disable_clock: 'Inaktivera klocka', 865 | am_pm_clock: 'AM/PM clock', 866 | disable_welcome_message: 'Inaktivera välkomstmeddelande', 867 | settings: 'Globala inställningar', 868 | dashboard_information: 'Dashboardinformation', 869 | alarm_entity: 'Larmentitet', 870 | weather_entity: 'Väderentitet', 871 | greeting_morning: 'God morgon', 872 | greeting_afternoon: 'God middag', 873 | greeting_evening: 'God kväll', 874 | v2_mode: 'Aktivera Dwains Dashboard v2-läge (layout)', 875 | disable_sensor_graph: 'Inaktivera visning av sensor som graf', 876 | }, 877 | editor: { 878 | lovelace_card: 'Lovelacekort', 879 | create_lovelace_card: 'Skapa ett nytt lovelacekort från början', 880 | dwains_dashboard_blueprint: 'Dwains Dashboard-blueprint', 881 | use_dwains_dashboard_blueprint: 'Använd en Dwains Dashboard-blueprint för att skapa ett kort', 882 | row_span: 'Radspann', 883 | row: 'Rad', 884 | rows: 'Rader', 885 | col_span: 'Kolumnspann', 886 | column: 'Kolumn', 887 | columns: 'Kolumner', 888 | default_col_row: 'Standardkolumn- och radstorlek', 889 | large_col_row: 'Kolumn- och radstorlek för stora skärmar', 890 | extra_large_col_row: 'Kolumn- och radstorlek för extra stora skärmar', 891 | }, 892 | entity: { 893 | title: 'Entitet', 894 | title_plural: 'entiteter', 895 | add_card_to: 'Lägg till kort till ', 896 | edit_entity: 'Redigera entitet', 897 | edit_entity_card: 'Redigera entitetskort', 898 | edit_entity_popup_card: 'Redigera entitets-pop up-kort', 899 | add_to_favorites: 'Lägg till i favoriter', 900 | remove_from_favorites: 'Ta bort från favoriter', 901 | popup_card: 'Pop up-kort', 902 | entity_card: 'Entitetskort', 903 | settings: 'Entitetsinställningar', 904 | group: 'Gruppera efter enheter', 905 | ungroup: 'Avgruppera efter enheter', 906 | enable: 'Aktivera entitet', 907 | disable: 'Inaktivera entitet i DD', 908 | disable_all: 'Inaktivera alla entiteter', 909 | hide_all: 'Göm alla entiteter', 910 | exclude: 'Exkludera entitet i DD', 911 | hide: 'Dölj entitet i DD', 912 | unhide: 'Ta fram entitet', 913 | use_popup_card: 'Använd eget pop up-kort', 914 | use_entity_card: 'Använd eget entitetskort', 915 | friendly_name: 'Byt namn för DD', 916 | hidden: 'Följande entiteter är dolda:', 917 | disabled: 'Följande entiteter är inaktiverade:', 918 | unavailable: 'Följande entiteter är otillgängliga:', 919 | }, 920 | favorite: { 921 | title: 'Favorit', 922 | title_plural: 'Favoriter', 923 | all_favorites: 'Alla favoriter', 924 | }, 925 | home: { 926 | title: 'Hem', 927 | }, 928 | area: { 929 | title: 'Område', 930 | title_plural: 'Områden', 931 | edit_area_button: 'Redigera områdesknapp', 932 | group_by_floor: 'Gruppera efter våningsplan', 933 | ungroup_by_floor: 'Avgruppera efter våningsplan', 934 | icon: 'Områdesikon', 935 | floor: 'Våningsplan', 936 | no_floor: 'Inget våningsplan', 937 | disable: 'Inaktivera område i DD', 938 | disabled: 'Följande områden är inaktiverade:', 939 | enable: 'Aktivera område', 940 | }, 941 | device: { 942 | title: 'Enhet', 943 | title_plural: 'enheter', 944 | edit_device_button: 'Redigera enhetsknapp', 945 | edit_device_card: 'Ställ in anpassade entitetskort för domän ', 946 | edit_device_popup: 'Ställ in anpassade entitetspopups för domän ', 947 | current_blueprint_card: 'Du använder för närvarande följande blueprint för alla entitetskort i domänen ', 948 | current_blueprint_popup: 'Du använder för närvarande följande blueprint för alla entitetspopups i domänen ', 949 | icon_required: 'Om du vill lägga till den till navigationslisten måste du välja en ikon!', 950 | icon: 'Enhetsikon', 951 | show_in_navbar: 'Lägg till enhetssida till huvudnavigationslisten', 952 | hide: 'Dölj enhetsöversikt', 953 | unhide: 'Ta fram enhetsöversikt', 954 | hidden: 'Följande enhetsöversikter är dolda', 955 | see_all: 'Se alla', 956 | turn_all_off: 'Stäng av alla', 957 | on: 'på', 958 | open: 'öppen', 959 | cover: 'Skydd', 960 | light: 'Belysning', 961 | climate: 'Klimat', 962 | sensor: 'Sensorer', 963 | binary_sensor: 'Binära sensorer', 964 | media_player: 'Mediaspelare', 965 | remote: 'Fjärrkontroll', 966 | scene: 'Scener', 967 | number: 'Nivåer', 968 | switch: 'Kontakter', 969 | button: 'Knappar', 970 | water_heater: 'Varmvattenberedare', 971 | camera: 'Kameror', 972 | select: 'Flervalslistor', 973 | vacuum: 'Dammsugare', 974 | fan: 'Fläktar', 975 | door: 'Dörr', 976 | window: 'Fönster', 977 | vibration: 'Vibration', 978 | motion: 'Rörelse', 979 | device_tracker: 'Enhetsspårare', 980 | lock: 'Lås', 981 | input_boolean: 'Växlare', 982 | weather: 'Väder', 983 | moisture: 'Fuktighet', 984 | input_select: 'Inmatningsval', 985 | carbon_monoxide: 'Kolmonoxid', 986 | gas: 'Gas', 987 | problem: 'Problem', 988 | safety: 'Säkerhet', 989 | smoke: 'Rök', 990 | tamper: 'Manipulation', 991 | update: 'Uppdatera', 992 | person: 'Person', 993 | alarm_control_panel: 'Larmkontrollpanel', 994 | automation: 'Automation', 995 | group: 'Gruppera efter områden', 996 | ungroup: 'Avgruppera efter områden', 997 | }, 998 | more: { 999 | title: 'Mer', 1000 | title_plural: 'Mersidor', 1001 | pages: 'sidor', 1002 | create: 'Skapa ny mersida', 1003 | edit: 'Redigera mersida', 1004 | name_required: 'Du måste ange ett namn för sidan', 1005 | icon_required: 'Om du vill lägga till den till navigationslisten måste du välja en ikon!', 1006 | add_navbar: 'Lägg till denna mersida till huvudnavigationslisten', 1007 | name: 'Namn på mersida', 1008 | icon: 'Ikon för mersida', 1009 | }, 1010 | blueprint: { 1011 | title: 'Blueprint', 1012 | title_plural: 'Blueprints', 1013 | yaml_required: 'Ingen YAML-kod inmatad!', 1014 | installed: 'Installerad', 1015 | no_blueprints_installed: 'Inga blueprints installerade', 1016 | not_installed: 'Inte installerad', 1017 | installed_blueprints: 'Installerade blueprints', 1018 | type: 'Typ av blueprint', 1019 | used_custom_cards: 'Använda skräddarsydda kort', 1020 | use: 'Använd denna blueprint', 1021 | install: 'Installera blueprint', 1022 | yaml_code: 'Blueprint YAML-kod', 1023 | instruction: 'Leta upp den blueprint du vill installera på Dwains Dashboard Community Blueprints Github och klistra in blueprintens YAML-kod nedanför. Efter en lyckad installation kommer lovelace och denna sida att laddas om. Du kan sedan använda den installerade blueprinten.', 1024 | } 1025 | }, 1026 | 1027 | // It Italian 1028 | it: { 1029 | global: { 1030 | dwains_dashboard_settings: 'Dwains Dashboard Impostazioni', 1031 | enable_edit_mode: 'Abilita la modalità di modifica', 1032 | disable_edit_mode: 'Disabilita la modalità di modifica', 1033 | version: 'Versione', 1034 | disable_clock: 'Disattiva orologio', 1035 | am_pm_clock: 'AM/PM clock', 1036 | disable_welcome_message: 'Disabilita il messaggio di Benvenuto', 1037 | settings: 'Impostazioni Globali', 1038 | dashboard_information: 'Inpostazioni Dashboard', 1039 | alarm_entity: 'Entità di allarme', 1040 | weather_entity: 'Entità meteorologica', 1041 | greeting_morning: 'Buon giorno', 1042 | greeting_afternoon: 'Buon pomeriggio', 1043 | greeting_evening: 'Buona serata', 1044 | v2_mode: 'Enable Dwains Dashboard v2 mode (layout)', 1045 | disable_sensor_graph: 'Disattiva la visualizzazione del sensore come grafico', 1046 | }, 1047 | editor: { 1048 | lovelace_card: 'Lovelace Card', 1049 | create_lovelace_card: 'Crea una nuova card lovelace da zero', 1050 | dwains_dashboard_blueprint: 'Dwains Dashboard Blueprint', 1051 | use_dwains_dashboard_blueprint: 'Usa Dwain Dashboard Blueprint per creare una carta', 1052 | row_span: 'Intervallo di riga', 1053 | row: 'Riga', 1054 | rows: 'Righe', 1055 | col_span: 'Col span', 1056 | column: 'Colonna', 1057 | columns: 'Colonne', 1058 | default_col_row: 'Colore predefinito e dimensione della riga', 1059 | large_col_row: 'Dimensione colonna e riga a schermo grande', 1060 | extra_large_col_row: 'Dimensione colonna e riga a schermo intero', 1061 | }, 1062 | entity: { 1063 | title: 'Entità', 1064 | title_plural: 'entità', 1065 | add_card_to: 'Aggiungi carta a', 1066 | edit_entity: 'Modifica entità', 1067 | edit_entity_card: 'Modifica scheda entità', 1068 | edit_entity_popup_card: 'Modifica scheda popup entità', 1069 | add_to_favorites: 'Aggiungi ai preferiti', 1070 | remove_from_favorites: 'Rimuovi dai preferiti', 1071 | popup_card: 'Scheda popup', 1072 | entity_card: 'Entità card', 1073 | settings: 'Entità Impostazioni', 1074 | group: 'Raggruppa per dispositivi', 1075 | ungroup: 'Separa per dispositivi', 1076 | enable: 'Abilita entità', 1077 | disable: 'Disabilita entità in DD', 1078 | disable_all: 'Disable all entities', 1079 | hide_all: 'Hide all entities', 1080 | exclude: 'Exclude entity in DD', 1081 | hide: 'Nascondi entità in DD', 1082 | unhide: 'Mostra entità', 1083 | use_popup_card: 'Usa la tua scheda popup', 1084 | use_entity_card: 'Usa la entity card', 1085 | friendly_name: 'Rinomina per DD', 1086 | hidden: 'Le seguenti entità sono nascoste:', 1087 | disabled: 'Le seguenti entità sono disabilitate:', 1088 | unavailable: 'Le seguenti entità non sono disponibili:', 1089 | }, 1090 | favorite: { 1091 | title: 'Preferito', 1092 | title_plural: 'Preferiti', 1093 | all_favorites: 'Tutti i preferiti', 1094 | }, 1095 | home: { 1096 | title: 'Home', 1097 | }, 1098 | area: { 1099 | title: 'Zona', 1100 | title_plural: 'Zone', 1101 | edit_area_button: 'Modifica pulsante area', 1102 | group_by_floor: 'Raggruppa per piano', 1103 | ungroup_by_floor: 'Separa per piano', 1104 | icon: 'Icona della zona', 1105 | floor: 'Piano della zona', 1106 | no_floor: 'Nessun pavimento', 1107 | disable: 'Disable area in DD', 1108 | disabled: 'The following areas are disabled:', 1109 | enable: 'Enable area', 1110 | }, 1111 | device: { 1112 | title: 'Dispositivo', 1113 | title_plural: 'Dispositivi', 1114 | edit_device_button: 'Pulsante Modifica dispositivo', 1115 | edit_device_card: 'Imposta la scheda entità personalizzate per il dominio ', 1116 | edit_device_popup: 'Imposta il popup di entità personalizzate per il dominio ', 1117 | current_blueprint_card: 'Attualmente stai utilizzando il seguente progetto per tutte le schede entità nel dominio ', 1118 | current_blueprint_popup: 'Attualmente stai utilizzando il seguente blueprint per tutti i popup di entità nel dominio ', 1119 | icon_required: 'Se vuoi aggiungerlo alla barra di navigazione devi selezionare una icona!', 1120 | icon: 'Icona del dispositivo', 1121 | show_in_navbar: 'Aggiungi la pagina del dispositivo nella barra di navigazione principale', 1122 | hide: 'Hide device overview', 1123 | unhide: 'Unhide device overview', 1124 | hidden: 'The following device overviews are hidden', 1125 | see_all: 'Vedi tutto', 1126 | turn_all_off: 'Turn all off', 1127 | on: 'su', 1128 | open: 'aprire', 1129 | cover: 'Coperchio', 1130 | light: 'Luce', 1131 | climate: 'Clima', 1132 | sensor: 'Sensore', 1133 | binary_sensor: 'Sensore binario', 1134 | media_player: 'Media player', 1135 | remote: 'A Distanza', 1136 | scene: 'Scena', 1137 | number: 'Numero', 1138 | switch: 'Interruttore', 1139 | button: 'Bottone', 1140 | water_heater: 'Scaldabagno', 1141 | camera: 'Camera', 1142 | select: 'Selezionato', 1143 | vacuum: 'Aspirapolvere', 1144 | fan: 'Ventilatore', 1145 | door: 'Porta', 1146 | window: 'Finestra', 1147 | vibration: 'Vibrazione', 1148 | motion: 'Movimento', 1149 | device_tracker: 'Localizzatore di dispositivi', 1150 | lock: 'Serratura', 1151 | input_boolean: 'Input booleano', 1152 | weather: 'Condizioni meteo', 1153 | moisture: 'Umidità', 1154 | input_select: 'Seleziona input', 1155 | carbon_monoxide: 'Monossido di carbonio', 1156 | gas: 'Gas', 1157 | problem: 'Problema', 1158 | safety: 'Sicurezza', 1159 | smoke: 'Fumo', 1160 | tamper: 'Manomettere', 1161 | update: 'Aggiornare', 1162 | person: 'Persona', 1163 | alarm_control_panel: 'Pannello di controllo allarme', 1164 | automation: 'Automation', 1165 | group: 'Group by areas', 1166 | ungroup: 'Ungroup by areas', 1167 | }, 1168 | more: { 1169 | title: 'Di più', 1170 | title_plural: 'Più pagine', 1171 | pages: 'pagine', 1172 | create: 'Crea una nuova pagina', 1173 | edit: 'Modifica più pagina', 1174 | name_required: 'È necessario specificare un nome per la pagina', 1175 | icon_required: 'Se vuoi aggiungerlo alla barra di navigazione devi selezionare una icona!', 1176 | add_navbar: 'Aggiungi questa pagina in più nella barra di navigazione principale', 1177 | name: 'Altro nome di pagina', 1178 | icon: 'Icona della pagina più', 1179 | }, 1180 | blueprint: { 1181 | title: 'Blueprint', 1182 | title_plural: 'Blueprints', 1183 | yaml_required: 'Nessun codice YAML inserito!', 1184 | installed: 'Installato', 1185 | no_blueprints_installed: 'Nessun blueprints installato', 1186 | not_installed: 'Non installato', 1187 | installed_blueprints: 'Blueprints Installati', 1188 | type: 'Tipo blueprint', 1189 | used_custom_cards: 'Carte personalizzate usate', 1190 | use: 'Usa questo blueprint', 1191 | install: 'installa blueprint', 1192 | yaml_code: 'Blueprint YAML code', 1193 | instruction: 'Cerca il progetto che desideri installare in Dwains Dashboard Community Blueprints Github e incolla il codice yaml del progetto di seguito. Dopo una corretta installazione, lovelace e questa pagina si ricaricherà. Quindi puoi utilizzare il progetto installato.', 1194 | } 1195 | }, 1196 | 1197 | //ES Español by Christian Villarreal 1198 | es: { 1199 | global: { 1200 | dwains_dashboard_settings: 'Opciones de Dwains Dashboard', 1201 | enable_edit_mode: 'Habilitar modo edición', 1202 | disable_edit_mode: 'Deshabilitar modo edición', 1203 | version: 'Version', 1204 | disable_clock: 'Desactivar reloj', 1205 | am_pm_clock: 'AM/PM clock', 1206 | disable_welcome_message: 'Desabilitar mensaje de bienvenida', 1207 | settings: 'Configuración global', 1208 | dashboard_information: 'información del Dashboard', 1209 | alarm_entity: 'Entidad alarma', 1210 | weather_entity: 'Entidad tiempo', 1211 | greeting_morning: 'Buenos días', 1212 | greeting_afternoon: 'Buenas tardes', 1213 | greeting_evening: 'Buenas noches', 1214 | v2_mode: 'Enable Dwains Dashboard v2 mode (layout)', 1215 | disable_sensor_graph: 'Desactivar la visualización del sensor como gráfico', 1216 | }, 1217 | editor: { 1218 | lovelace_card: 'Lovelace Card', 1219 | create_lovelace_card: 'Crea una nueva lovelace card desde cero', 1220 | dwains_dashboard_blueprint: 'Dwains Dashboard Blueprint', 1221 | use_dwains_dashboard_blueprint: 'Usar un Blueprint de Dwain Dashboard para crear una tarjeta', 1222 | row_span: 'Intervalo de filas', 1223 | row: 'Fila', 1224 | rows: 'Filas', 1225 | col_span: 'Intervalo de Columnas', 1226 | column: 'Columna', 1227 | columns: 'Columnas', 1228 | default_col_row: 'Tamaño predeterminado de columna y fila', 1229 | large_col_row: 'Tamaño de columna y fila grande', 1230 | extra_large_col_row: 'Tamaño de columna y fila extra grande', 1231 | }, 1232 | entity: { 1233 | title: 'Entidad', 1234 | title_plural: 'Entidades', 1235 | add_card_to: 'Agregar tarjeta a ', 1236 | edit_entity: 'Editar entidad', 1237 | edit_entity_card: 'Editar tarjeta de entidad', 1238 | edit_entity_popup_card: 'Editar tarjeta emergente de entidad', 1239 | add_to_favorites: 'Agregar a favoritos', 1240 | remove_from_favorites: 'Quitar de favoritos', 1241 | popup_card: 'Tarjeta emergente', 1242 | entity_card: 'Tarjeta de entidad', 1243 | settings: 'Configuración de entidad', 1244 | group: 'Agrupar por dispositivos', 1245 | ungroup: 'Desagrupar por dispositivos', 1246 | enable: 'Habilitar entidad', 1247 | disable: 'Deshabilitar entidad en DD', 1248 | disable_all: 'Disable all entities', 1249 | hide_all: 'Hide all entities', 1250 | exclude: 'Exclude entity in DD', 1251 | hide: 'Ocultar entidad en DD', 1252 | unhide: 'Mostrar entidad', 1253 | use_popup_card: 'Utilizar su propia tarjeta emergente', 1254 | use_entity_card: 'Utilice su propia tarjeta de entidad', 1255 | friendly_name: 'Renombrar en DD', 1256 | hidden: 'Las siguientes entidades están ocultas:', 1257 | disabled: 'Las siguientes entidades estan deshabilitadas:', 1258 | unavailable: 'Las siguientes entidades no están disponibles:', 1259 | }, 1260 | favorite: { 1261 | title: 'Favorito', 1262 | title_plural: 'Favoritos', 1263 | all_favorites: 'Todos los favoritos', 1264 | }, 1265 | home: { 1266 | title: 'Home', 1267 | }, 1268 | area: { 1269 | title: 'Habitación', 1270 | title_plural: 'Habitaciónes', 1271 | edit_area_button: 'Editar Habitación', 1272 | group_by_floor: 'Agrupar por piso', 1273 | ungroup_by_floor: 'Desagrupar por piso', 1274 | icon: 'Icono de Habitación', 1275 | floor: 'Piso de Habitación', 1276 | no_floor: 'Sin piso', 1277 | disable: 'Disable area in DD', 1278 | disabled: 'The following areas are disabled:', 1279 | enable: 'Enable area', 1280 | }, 1281 | device: { 1282 | title: 'Dispositivo', 1283 | title_plural: 'Dispositivos', 1284 | edit_device_button: 'Editar dispositivo', 1285 | edit_device_card: 'Establecer tarjeta de entidades personalizadas para el dominio ', 1286 | edit_device_popup: 'Establecer una ventana emergente de entidades personalizadas para el dominio', 1287 | current_blueprint_card: 'Actualmente está utilizando el siguiente modelo para todas las tarjetas de entidades en el dominio', 1288 | current_blueprint_popup: 'Actualmente está utilizando el siguiente modelo para todas las ventanas emergentes de entidades en el dominio', 1289 | icon_required: 'Si desea agregarlo a la barra de navegación, debe seleccionar un icono.', 1290 | icon: 'Icono de dispositivo', 1291 | show_in_navbar: 'Agregar página de dispositivo en la barra de navegación principal', 1292 | hide: 'Hide device overview', 1293 | unhide: 'Unhide device overview', 1294 | hidden: 'The following device overviews are hidden', 1295 | see_all: 'Ver todos', 1296 | turn_all_off: 'Turn all off', 1297 | on: 'on', 1298 | open: 'Abierto', 1299 | cover: 'Cover', 1300 | light: 'Luz', 1301 | climate: 'Clima', 1302 | sensor: 'Sensor', 1303 | binary_sensor: 'Sensor binario', 1304 | media_player: 'Reproductor multimedia', 1305 | remote: 'Control remoto', 1306 | scene: 'Escena', 1307 | number: 'Número', 1308 | switch: 'Interruptor', 1309 | button: 'Botón', 1310 | water_heater: 'Calentador de agua', 1311 | camera: 'Cámara', 1312 | select: 'seleccione', 1313 | vacuum: 'Aspiradora', 1314 | fan: 'Ventilador', 1315 | door: 'Puerta', 1316 | window: 'Ventana', 1317 | vibration: 'Vibración', 1318 | motion: 'Movimiento', 1319 | device_tracker: 'Rastreador de dispositivo', 1320 | lock: 'Bloquear', 1321 | input_boolean: 'Entrada booleana', 1322 | weather: 'Clima', 1323 | moisture: 'Humedad', 1324 | input_select: 'Selección de entrada', 1325 | carbon_monoxide: 'Monoxido de carbono', 1326 | gas: 'Gas', 1327 | problem: 'Problema', 1328 | safety: 'Seguridad', 1329 | smoke: 'Humo', 1330 | tamper: 'Manipular', 1331 | update: 'Actualizar', 1332 | person: 'Persona', 1333 | alarm_control_panel: 'Panel de control de Alarma', 1334 | automation: 'Automation', 1335 | group: 'Group by areas', 1336 | ungroup: 'Ungroup by areas', 1337 | }, 1338 | more: { 1339 | title: 'Más', 1340 | title_plural: 'Páginas extra ', 1341 | pages: 'páginas', 1342 | create: 'Crear nueva página extra', 1343 | edit: 'Editar página extra', 1344 | name_required: 'Debe especificar un nombre para la página.', 1345 | icon_required: 'Si desea agregarla a la barra de navegación, debe seleccionar un icono.', 1346 | add_navbar: 'Agregar esta página extra en la barra de navegación principal', 1347 | name: 'Nombre de la página extra', 1348 | icon: 'Icono de la página extra', 1349 | }, 1350 | blueprint: { 1351 | title: 'Blueprint', 1352 | title_plural: 'Blueprints', 1353 | yaml_required: '¡No se ingresó ningún código YAML!', 1354 | installed: 'Instalado', 1355 | no_blueprints_installed: 'No hay blueprints instalados', 1356 | not_installed: 'No instalado', 1357 | installed_blueprints: 'Blueprints instalados', 1358 | type: 'Tipo de blueprint', 1359 | used_custom_cards: 'Tarjetas personalizadas usadas', 1360 | use: 'Usar este blueprint', 1361 | install: 'Instalar blueprint', 1362 | yaml_code: 'Blueprint código YAML', 1363 | instruction: 'Busque el blueprint que desea instalar en Dwains Dashboard Community Blueprints Github y pegue el código yaml del blueprint a continuación. Después de una instalación exitosa, lovelace y esta página se volverá a cargar. Entonces podrás usar el plano instalado.', 1364 | }, 1365 | }, 1366 | 1367 | //PL Polish 1368 | pl: { 1369 | global: { 1370 | dwains_dashboard_settings: 'Ustawenia Dwains Dashboard', 1371 | enable_edit_mode: 'Edytuj', 1372 | disable_edit_mode: 'Wyłącz edycję', 1373 | version: 'Wersja', 1374 | disable_clock: 'Wyłącz zegar', 1375 | am_pm_clock: 'AM/PM clock', 1376 | disable_welcome_message: 'Wyłącz wiadomość powitalną', 1377 | settings: 'Ustawienia ogólne', 1378 | dashboard_information: 'Informacje o Dashboard', 1379 | alarm_entity: 'Encja alarmu', 1380 | weather_entity: 'Encja pogody', 1381 | greeting_morning: 'Dzień Dobry', 1382 | greeting_afternoon: 'Miłego popołudnia', 1383 | greeting_evening: 'Dobry wieczór', 1384 | v2_mode: 'Włącz tryb Dwains Dashboard v2 (wygląd)', 1385 | disable_sensor_graph: 'Wyłącz wyświetlanie czujnika jako wykresu', 1386 | }, 1387 | editor: { 1388 | lovelace_card: 'Karta Lovelace', 1389 | create_lovelace_card: 'Utwórz nową kartę Lovelace', 1390 | dwains_dashboard_blueprint: 'Schemat Dwains Dashboard', 1391 | use_dwains_dashboard_blueprint: 'Użyj schematu Dwains Dashboard do stworzenia karty', 1392 | row_span: 'Szerokość wierszy', 1393 | row: 'Wiersz', 1394 | rows: 'Wiersze', 1395 | col_span: 'Szerokość kolumn', 1396 | column: 'Kolumna', 1397 | columns: 'Kolumny', 1398 | default_col_row: 'Domyślna szerokość kolumn i wierszy', 1399 | large_col_row: 'Duży ekran - szerokość kolumn i wierszy', 1400 | extra_large_col_row: 'Wielki ekran - szerokość kolumn i wierszy', 1401 | }, 1402 | entity: { 1403 | title: 'Encja', 1404 | title_plural: 'Encje', 1405 | add_card_to: 'Dodaj kartę do: ', 1406 | edit_entity: 'Edytuj encję', 1407 | edit_entity_card: 'Edytuj kartę encji', 1408 | edit_entity_popup_card: 'Edytuj wyskakującą kartę', 1409 | add_to_favorites: 'Dodaj do Ulubionych', 1410 | remove_from_favorites: 'Usuń z Ulubionych', 1411 | popup_card: 'Wyskakująca karta', 1412 | entity_card: 'Karta encji', 1413 | settings: 'Ustawienia encji', 1414 | group: 'Grupuj według urządzeń', 1415 | ungroup: 'Rozgrupuj według urządzeń', 1416 | enable: 'Włącz encję', 1417 | disable: 'Wyłącz encję w DD', 1418 | disable_all: 'Wyłącz wszystkie encje', 1419 | hide_all: 'Ukryj wszystkie encje', 1420 | exclude: 'Wyłącz encje w DD', 1421 | hide: 'Ukryj encje w DD', 1422 | unhide: 'Odkryj encje', 1423 | use_popup_card: 'Użyj własnej wyskakującej karty', 1424 | use_entity_card: 'Użyj własnej karty encji', 1425 | friendly_name: 'Przyjazna nazwa DD:', 1426 | hidden: 'Ukryte encje:', 1427 | disabled: 'Wyłączone encje:', 1428 | unavailable: 'Niedostępne encje:', 1429 | }, 1430 | favorite: { 1431 | title: 'Ulubione', 1432 | title_plural: 'Ulubione', 1433 | all_favorites: 'Wszystkie Ulubione', 1434 | }, 1435 | home: { 1436 | title: 'Strona główna', 1437 | }, 1438 | area: { 1439 | title: 'Obszar', 1440 | title_plural: 'Obszary', 1441 | edit_area_button: 'Edytuj przycisk obszaru', 1442 | group_by_floor: 'Grupuj według pięter', 1443 | ungroup_by_floor: 'Rozgrupuj według pięter', 1444 | icon: 'Ikony obszarów', 1445 | floor: 'Obszar pięter', 1446 | no_floor: 'Brak pięter', 1447 | disable: 'Wyłącz obszar', 1448 | disabled: 'Obszary wyłączone:', 1449 | enable: 'Włącz obszar', 1450 | }, 1451 | device: { 1452 | title: 'Urządzenie', 1453 | title_plural: 'Urządzenia', 1454 | edit_device_button: 'Edytuj przycisk urządzenia', 1455 | edit_device_card: 'Edytuj własną encję karty dla domeny', 1456 | edit_device_popup: 'Edytuj własną wyskakującą encję dla domeny', 1457 | current_blueprint_card: 'Obecnie używany schemat dla wszystkich encji w domenie: ', 1458 | current_blueprint_popup: 'Obecnie używany schemat dla wszystkich wyskakujących encji w domenie: ', 1459 | icon_required: 'Jeśli chcesz dodać urządzenie do paska nawigacyjnego, wybierz jego ikonę!', 1460 | icon: 'Ikony urządzeń:', 1461 | show_in_navbar: 'Pokaż urządzenie w pasku nawigacyjnym', 1462 | hide: 'Ukryj przegląd urządzeń', 1463 | unhide: 'Pokaż przegląd urządzeń', 1464 | hidden: 'Urządzenia ukryte:', 1465 | see_all: 'Zobacz wszystkie', 1466 | turn_all_off: 'Wyłącz wszystkie', 1467 | on: 'Włączony', 1468 | open: 'Otwarty', 1469 | cover: 'Roleta', 1470 | light: 'Światło', 1471 | climate: 'Termostat', 1472 | sensor: 'Sensor', 1473 | binary_sensor: 'Sensor binarny', 1474 | media_player: 'Odtwarzacz multimediów', 1475 | remote: 'Zdalny', 1476 | scene: 'Scena', 1477 | number: 'Liczba', 1478 | switch: 'Przełącznik', 1479 | button: 'Przycisk', 1480 | water_heater: 'Podgrzewacz wody', 1481 | camera: 'Kamera', 1482 | select: 'Wybierz', 1483 | vacuum: 'Odkurzacz', 1484 | fan: 'Wentylator', 1485 | door: 'Drzwi', 1486 | window: 'Okno', 1487 | vibration: 'Wibracja', 1488 | motion: 'Ruch', 1489 | device_tracker: 'Śledzenie urządzeń', 1490 | lock: 'Zamek', 1491 | input_boolean: 'Wybór przełącznika', 1492 | weather: 'Pogoda', 1493 | moisture: 'Wilgoć', 1494 | input_select: 'Wybór wejścia', 1495 | carbon_monoxide: 'Tlenek węgla', 1496 | gas: 'Gaz', 1497 | problem: 'Problem', 1498 | safety: 'Bezpieczeństwo', 1499 | smoke: 'Dym', 1500 | tamper: 'Sabotaż', 1501 | update: 'Aktualizacja', 1502 | person: 'Osoba', 1503 | alarm_control_panel: 'Panel alarmu', 1504 | automation: 'Automatyka', 1505 | group: 'Grupuj wg obszarów', 1506 | ungroup: 'Rozgrupuj wg obszarów', 1507 | }, 1508 | more: { 1509 | title: 'Więcej', 1510 | title_plural: 'Więcej stron', 1511 | pages: 'Strony', 1512 | create: 'Utwórz więcej nowych stron', 1513 | edit: 'Edytuj więcej stron', 1514 | name_required: 'Podaj nazwę strony:', 1515 | icon_required: 'Jeśli chcesz dodać stronę do paska nawigacyjnego, wybierz jej ikonę!', 1516 | add_navbar: 'Dodaj tę stronę do paska nawigacyjnego', 1517 | name: 'Więcej nazw stron', 1518 | icon: 'Więcej ikon strony', 1519 | }, 1520 | blueprint: { 1521 | title: 'Schemat', 1522 | title_plural: 'Schematy', 1523 | yaml_required: 'Nie wprowadzono kodu YAML!', 1524 | installed: 'Zainstalowane', 1525 | no_blueprints_installed: 'Brak zainstalowanego schematu', 1526 | not_installed: 'Nie zainstalowano', 1527 | installed_blueprints: 'Zainstalowane schematy:', 1528 | type: 'Typ schematu', 1529 | used_custom_cards: 'Używane karty niestandardowe:', 1530 | use: 'Użyj tego schematu', 1531 | install: 'Zainstaluj schemat', 1532 | yaml_code: 'Kod YAML schematu', 1533 | instruction: 'Odszukaj żądany schemat na Dwains Dashboard Community Blueprints Github i wklej kod YAML. Po instalacji lovelace strona zostanie ponownie załadowana abyś mógł użyć zainstalowanego schematu. ', 1534 | } 1535 | }, 1536 | 1537 | //ZH Chinese 1538 | zh: { 1539 | global: { 1540 | dwains_dashboard_settings: 'Dwains 仪表板设置', 1541 | enable_edit_mode: '启用编辑模式', 1542 | disable_edit_mode: '禁用编辑模式', 1543 | version: '版本', 1544 | disable_clock: '禁用时钟', 1545 | am_pm_clock: 'AM/PM 时钟', 1546 | disable_welcome_message: '禁用欢迎消息', 1547 | settings: '全局设置', 1548 | dashboard_information: '仪表板信息', 1549 | alarm_entity: '警报实体', 1550 | weather_entity: '天气实体', 1551 | greeting_morning: '早上好', 1552 | greeting_afternoon: '下午好', 1553 | greeting_evening: '晚上好', 1554 | v2_mode: '启用 Dwains 仪表板 v2 模式(布局)', 1555 | disable_sensor_graph: '禁用将传感器显示为图表', 1556 | }, 1557 | editor: { 1558 | lovelace_card: 'Lovelace 卡片', 1559 | create_lovelace_card: '从头开始创建新的 Lovelace 卡片', 1560 | dwains_dashboard_blueprint: 'Dwains 仪表板蓝图', 1561 | use_dwains_dashboard_blueprint: '使用 Dwain 仪表板蓝图创建卡片', 1562 | row_span: '行跨度', 1563 | row: '行', 1564 | rows: '行', 1565 | col_span: '列跨度', 1566 | column: '列', 1567 | columns: '列', 1568 | default_col_row: '默认列和行大小', 1569 | large_col_row: '大屏幕列和行大小', 1570 | extra_large_col_row: '特大屏幕列和行大小', 1571 | }, 1572 | entity: { 1573 | title: '实体', 1574 | title_plural: '实体', 1575 | add_card_to: '将卡片添加到', 1576 | edit_entity: '编辑实体', 1577 | edit_entity_card: '编辑实体卡片', 1578 | edit_entity_popup_card: '编辑实体弹出卡片', 1579 | add_to_favorites: '添加到收藏夹', 1580 | remove_from_favorites: '从收藏夹中删除', 1581 | popup_card: '弹出卡片', 1582 | entity_card: '实体卡片', 1583 | settings: '实体设置', 1584 | group: '按设备分组', 1585 | ungroup: '取消按设备分组', 1586 | enable: '启用实体', 1587 | disable: '在 DD 中禁用实体', 1588 | disable_all: '禁用所有实体', 1589 | hide_all: '隐藏所有实体', 1590 | exclude: '在 DD 中排除实体', 1591 | hide: '在 DD 中隐藏实体', 1592 | unhide: '取消隐藏实体', 1593 | use_popup_card: '使用自己的弹出卡片', 1594 | use_entity_card: '使用自己的实体卡片', 1595 | friendly_name: '为 DD 重命名', 1596 | hidden: '以下实体已隐藏:', 1597 | disabled: '以下实体已禁用:', 1598 | unavailable: '以下实体不可用:', 1599 | }, 1600 | favorite: { 1601 | title: '收藏夹', 1602 | title_plural: '收藏夹', 1603 | all_favorites: '所有收藏夹', 1604 | }, 1605 | home: { 1606 | title: '首页', 1607 | }, 1608 | area: { 1609 | title: '区域', 1610 | title_plural: '区域', 1611 | edit_area_button: '编辑区域按钮', 1612 | group_by_floor: '按楼层分组', 1613 | ungroup_by_floor: '取消按楼层分组', 1614 | icon: '区域图标', 1615 | floor: '区域楼层', 1616 | no_floor: '没有楼层', 1617 | disable: '在 DD 中禁用区域', 1618 | disabled: '以下区域已禁用:', 1619 | enable: '启用区域', 1620 | }, 1621 | device: { 1622 | title: '设备', 1623 | title_plural: '设备', 1624 | edit_device_button: '编辑设备按钮', 1625 | edit_device_card: '为领域设置自定义实体卡片', 1626 | edit_device_popup: '为领域设置自定义弹出实体', 1627 | current_blueprint_card: '您当前正在使用以下蓝图为领域中所有实体卡片:', 1628 | current_blueprint_popup: '您当前正在使用以下蓝图为领域中所有弹出实体:', 1629 | icon_required: '如果要将其添加到导航栏,必须选择一个图标!', 1630 | icon: '设备图标', 1631 | show_in_navbar: '在主导航栏中添加设备页面', 1632 | hide: '隐藏设备概述', 1633 | unhide: '取消隐藏设备概述', 1634 | hidden: '以下设备概述已隐藏', 1635 | see_all: '查看全部', 1636 | turn_all_off: '全部关闭', 1637 | on: '开', 1638 | open: '打开', 1639 | cover: '盖', 1640 | light: '灯', 1641 | climate: '气候', 1642 | sensor: '传感器', 1643 | binary_sensor: '二进制传感器', 1644 | media_player: '媒体播放器', 1645 | remote: '遥控器', 1646 | scene: '场景', 1647 | number: '数字', 1648 | switch: '开关', 1649 | button: '按钮', 1650 | water_heater: '热水器', 1651 | camera: '摄像头', 1652 | select: '选择', 1653 | vacuum: '吸尘器', 1654 | fan: '风扇', 1655 | door: '门', 1656 | window: '窗户', 1657 | vibration: '振动', 1658 | motion: '运动', 1659 | device_tracker: '设备追踪器', 1660 | lock: '锁', 1661 | input_boolean: '输入布尔值', 1662 | weather: '天气', 1663 | moisture: '湿度', 1664 | input_select: '输入选择', 1665 | carbon_monoxide: '一氧化碳', 1666 | gas: '气体', 1667 | problem: '问题', 1668 | safety: '安全', 1669 | smoke: '烟雾', 1670 | tamper: '篡改', 1671 | update: '更新', 1672 | person: '人员', 1673 | alarm_control_panel: '报警控制面板', 1674 | automation: '自动化', 1675 | group: '按区域分组', 1676 | ungroup: '取消按区域分组', 1677 | update: '更新', 1678 | script: '脚本', 1679 | time: '时间', 1680 | event: '事件', 1681 | text: '文本', 1682 | }, 1683 | more: { 1684 | title: '更多', 1685 | title_plural: '更多页面', 1686 | pages: '页', 1687 | create: '创建新的更多页面', 1688 | edit: '编辑更多页面', 1689 | name_required: '您必须为页面指定名称', 1690 | icon_required: '如果要添加到导航栏,必须选择一个图标!', 1691 | add_navbar: '将此更多页面添加到主导航栏', 1692 | name: '更多页面名称', 1693 | icon: '更多页面图标', 1694 | }, 1695 | blueprint: { 1696 | title: '蓝图', 1697 | title_plural: '蓝图', 1698 | yaml_required: '未输入 YAML 代码!', 1699 | installed: '已安装', 1700 | no_blueprints_installed: '未安装蓝图', 1701 | not_installed: '未安装', 1702 | installed_blueprints: '已安装蓝图', 1703 | type: '类型蓝图', 1704 | used_custom_cards: '已使用自定义卡片', 1705 | use: '使用此蓝图', 1706 | install: '安装蓝图', 1707 | yaml_code: '蓝图 YAML 代码', 1708 | instruction: '在 Dwains Dashboard Community Blueprints Github 中查找要安装的蓝图,并将蓝图 YAML 代码粘贴到下面。安装成功后,Lovelace 和此页面将重新加载。然后,您可以使用已安装的蓝图。', 1709 | }, 1710 | }, 1711 | }; 1712 | 1713 | export default translations; 1714 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/js/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: [ 5 | './src/dwains-navigation-card.js', 6 | './src/dwains-dashboard.js', 7 | './src/dwains-dashboard-layout.js', 8 | './src/dwains-homepage-card.js', 9 | './src/dwains-more-pages-card.js', 10 | './src/dwains-more-page-card.js', 11 | './src/dwains-edit-more-page-card.js', 12 | './src/dwains-notification-card.js', 13 | './src/dwains-house-information-card.js', 14 | './src/dwains-house-information-more-info-card.js', 15 | './src/dwains-blueprint-card.js', 16 | './src/dwains-devicespage-card.js', 17 | //'./src/dwains-thermostat-card.js', 18 | //'./src/dwains-light-card.js', 19 | //'./src/dwains-cover-card.js', 20 | //'./src/dwains-button-card.js', 21 | './src/dwains-flexbox-card.js', 22 | './src/dwains-heading-card.js', 23 | './src/dwains-create-custom-card-card.js', 24 | './src/dwains-edit-area-button-card.js', 25 | './src/dwains-edit-entity-card-card.js', 26 | './src/dwains-edit-entity-card.js', 27 | './src/dwains-edit-entity-popup-card.js', 28 | './src/dwains-edit-homepage-header-card.js', 29 | './src/dwains-edit-device-card-card.js', 30 | './src/dwains-edit-device-popup-card.js', 31 | './src/dwains-edit-device-button-card.js', 32 | './src/dwains-popup.js', 33 | ], 34 | mode: 'production', 35 | output: { 36 | filename: 'dwains-dashboard.js', 37 | path: path.resolve(__dirname) 38 | }, 39 | devtool: "source-map" 40 | }; 41 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/js/webpack.config.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwainscheeren/dwains-lovelace-dashboard/dcea61701e0efcb9041b977a8c1063055b4172d3/custom_components/dwains_dashboard/js/webpack.config.js.gz -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/load_dashboard.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from homeassistant.components.lovelace.dashboard import LovelaceYAML 4 | from homeassistant.components.lovelace import _register_panel 5 | 6 | from .const import DOMAIN 7 | 8 | _LOGGER = logging.getLogger(__name__) 9 | 10 | def load_dashboard(hass, config_entry): 11 | 12 | #_LOGGER.warning(config_entry.options) 13 | #_LOGGER.warning(config_entry.options["sidepanel_title"]) 14 | 15 | sidepanel_title = "Dwains Dashboard" 16 | sidepanel_icon = "mdi:alpha-d-box" 17 | 18 | if("sidepanel_title" in config_entry.options): 19 | sidepanel_title = config_entry.options["sidepanel_title"] 20 | 21 | if("sidepanel_icon" in config_entry.options): 22 | sidepanel_icon = config_entry.options["sidepanel_icon"] 23 | 24 | dashboard_url = "dwains-dashboard" 25 | dashboard_config = { 26 | "mode": "yaml", 27 | "icon": sidepanel_icon, 28 | "title": sidepanel_title, 29 | "filename": "custom_components/dwains_dashboard/lovelace/ui-lovelace.yaml", 30 | "show_in_sidebar": True, 31 | "require_admin": False, 32 | } 33 | 34 | hass.data["lovelace"].dashboards[dashboard_url] = LovelaceYAML(hass, dashboard_url, dashboard_config) 35 | 36 | _register_panel(hass, dashboard_url, "yaml", dashboard_config, False) 37 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/load_plugins.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from homeassistant.components.frontend import add_extra_js_url 3 | from homeassistant.components.http import HomeAssistantHTTP 4 | from homeassistant.components.http import StaticPathConfig 5 | 6 | DATA_EXTRA_MODULE_URL = 'frontend_extra_module_url' 7 | 8 | _LOGGER = logging.getLogger(__name__) 9 | 10 | from .const import VERSION 11 | 12 | async def load_plugins(hass, name): 13 | #_LOGGER.warning(f"load_plugins() version: {VERSION}") 14 | 15 | add_extra_js_url(hass, f"/dwains_dashboard/js/dwains-dashboard.js?version={VERSION}") 16 | 17 | await hass.http.async_register_static_paths( 18 | [StaticPathConfig("/dwains_dashboard/js", hass.config.path(f"custom_components/{name}/js"), True)] 19 | ) 20 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/lovelace/ui-lovelace.yaml: -------------------------------------------------------------------------------- 1 | # Start of the dwains dashboard ui-lovelace.yaml 2 | 3 | #Load all settings for Dwains Dashboard 4 | dwains_dashboard: 5 | active: true 6 | 7 | #For people who want to use button card templates 8 | button_card_templates: 9 | !include_dir_merge_named ../../../dwains-dashboard/button_card_templates 10 | 11 | #For people who want to use apexcharts card templates 12 | apexcharts_card_templates: 13 | !include_dir_merge_named ../../../dwains-dashboard/apexcharts_card_templates 14 | 15 | # Start of main lovelace theme 16 | lovelace-background: var(--background-image) 17 | views: 18 | !include_dir_merge_list views/ 19 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/lovelace/views/01.homepage.yaml: -------------------------------------------------------------------------------- 1 | - title: Home 2 | icon: mdi:home 3 | path: home 4 | type: custom:dwains-dashboard-layout 5 | cards: 6 | - type: custom:homepage-card -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/lovelace/views/02.devices.yaml: -------------------------------------------------------------------------------- 1 | - title: Devices 2 | icon: mdi:format-list-bulleted-type 3 | path: devices 4 | type: custom:dwains-dashboard-layout 5 | cards: 6 | - type: custom:devices-card -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/lovelace/views/04.more_page_view.yaml: -------------------------------------------------------------------------------- 1 | # dwains_dashboard 2 | 3 | #More_page addon view 4 | {% if _dd_more_pages %} 5 | {% for addon in _dd_more_pages %} 6 | - title: {{ _dd_more_pages[addon]["name"] }} 7 | path: more_page_{{ addon|lower|replace("'", "_")|replace(" ", "_") }} 8 | type: custom:dwains-dashboard-layout 9 | icon: {{ _dd_more_pages[addon]["icon"] }} 10 | visible: true 11 | cards: 12 | - type: custom:more-page-card 13 | name: {{ _dd_more_pages[addon]["name"] }} 14 | icon: {{ _dd_more_pages[addon]["icon"] }} 15 | show_in_navbar: {{ _dd_more_pages[addon]["show_in_navbar"] }} 16 | foldername: {{ addon }} 17 | card: !include ../../../../{{ _dd_more_pages[addon]["path"] }} 18 | {% endfor %} 19 | {% endif %} -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/lovelace/views/05.more_pages.yaml: -------------------------------------------------------------------------------- 1 | - title: More page 2 | path: more_page 3 | icon: mdi:dots-horizontal 4 | visible: true 5 | type: custom:dwains-dashboard-layout 6 | cards: 7 | - type: custom:more-pages-card -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "dwains_dashboard", 3 | "name": "Dwains Dashboard", 4 | "documentation": "https://dwainscheeren.github.io/dwains-lovelace-dashboard/", 5 | "issue_tracker": "https://github.com/dwainscheeren/dwains-lovelace-dashboard/issues", 6 | "dependencies": ["lovelace", "http", "frontend"], 7 | "codeowners": ["@dwainscheeren"], 8 | "config_flow": true, 9 | "version": "3.8.0", 10 | "iot_class": "calculated", 11 | "homeassistant": "2025.4.0" 12 | } -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/notifications.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import json 4 | import io 5 | import time 6 | import voluptuous as vol 7 | import homeassistant.util.dt as dt_util 8 | 9 | from collections import OrderedDict 10 | from typing import Any, Mapping, MutableMapping, Optional 11 | 12 | from homeassistant.components import websocket_api 13 | from homeassistant.core import HomeAssistant, callback 14 | from homeassistant.helpers import config_validation as cv 15 | from homeassistant.helpers.entity import Entity, async_generate_entity_id 16 | from homeassistant.loader import bind_hass 17 | from homeassistant.util import slugify 18 | 19 | from .const import DOMAIN 20 | 21 | ATTR_CREATED_AT = "created_at" 22 | ATTR_MESSAGE = "message" 23 | ATTR_NOTIFICATION_ID = "notification_id" 24 | ATTR_TITLE = "title" 25 | ATTR_STATUS = "status" 26 | 27 | ENTITY_ID_FORMAT = DOMAIN + ".{}" 28 | 29 | EVENT_DWAINS_dashboard_NOTIFICATIONS_UPDATED = "dwains_dashboard_notifications_updated" 30 | 31 | SERVICE_CREATE = "notification_create" 32 | SERVICE_DISMISS = "notification_dismiss" 33 | SERVICE_MARK_READ = "notification_mark_read" 34 | 35 | SCHEMA_SERVICE_CREATE = vol.Schema( 36 | { 37 | vol.Required(ATTR_MESSAGE): cv.template, 38 | vol.Optional(ATTR_TITLE): cv.template, 39 | vol.Optional(ATTR_NOTIFICATION_ID): cv.string, 40 | } 41 | ) 42 | 43 | SCHEMA_SERVICE_DISMISS = vol.Schema({vol.Required(ATTR_NOTIFICATION_ID): cv.string}) 44 | 45 | SCHEMA_SERVICE_MARK_READ = vol.Schema({vol.Required(ATTR_NOTIFICATION_ID): cv.string}) 46 | 47 | DEFAULT_OBJECT_ID = "notification" 48 | 49 | STATE = "notifying" 50 | STATUS_UNREAD = "unread" 51 | STATUS_READ = "read" 52 | 53 | #Notifications part 54 | @bind_hass 55 | def create(hass, message, title=None, notification_id=None): 56 | """Generate a notification.""" 57 | hass.add_job(async_create, hass, message, title, notification_id) 58 | 59 | @bind_hass 60 | def dismiss(hass, notification_id): 61 | """Remove a notification.""" 62 | hass.add_job(async_dismiss, hass, notification_id) 63 | 64 | @callback 65 | @bind_hass 66 | def async_create( 67 | hass: HomeAssistant, 68 | message: str, 69 | title: Optional[str] = None, 70 | notification_id: Optional[str] = None, 71 | ) -> None: 72 | """Generate a notification.""" 73 | data = { 74 | key: value 75 | for key, value in [ 76 | (ATTR_TITLE, title), 77 | (ATTR_MESSAGE, message), 78 | (ATTR_NOTIFICATION_ID, notification_id), 79 | ] 80 | if value is not None 81 | } 82 | 83 | hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_CREATE, data)) 84 | 85 | @callback 86 | @bind_hass 87 | def async_dismiss(hass: HomeAssistant, notification_id: str) -> None: 88 | """Remove a notification.""" 89 | data = {ATTR_NOTIFICATION_ID: notification_id} 90 | 91 | hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_DISMISS, data)) 92 | 93 | @callback 94 | @websocket_api.websocket_command({vol.Required("type"): "dwains_dashboard_notification/get"}) 95 | def websocket_get_notifications( 96 | hass: HomeAssistant, 97 | connection: websocket_api.ActiveConnection, 98 | msg: Mapping[str, Any], 99 | ) -> None: 100 | """Return a list of dwains_dashboard_notifications.""" 101 | connection.send_message( 102 | websocket_api.result_message( 103 | msg["id"], 104 | [ 105 | { 106 | key: data[key] 107 | for key in ( 108 | ATTR_NOTIFICATION_ID, 109 | ATTR_MESSAGE, 110 | ATTR_STATUS, 111 | ATTR_TITLE, 112 | ATTR_CREATED_AT, 113 | ) 114 | } 115 | for data in hass.data[DOMAIN]["notifications"].values() 116 | ], 117 | ) 118 | ) 119 | #End notifications part 120 | 121 | def notifications(hass, name): 122 | #Notifications part setup 123 | """Set up the dwains dashboard notification component.""" 124 | 125 | dwains_dashboard_notifications: MutableMapping[str, MutableMapping] = OrderedDict() 126 | hass.data[DOMAIN]["notifications"] = dwains_dashboard_notifications 127 | 128 | @callback 129 | def create_service(call): 130 | """Handle a create notification service call.""" 131 | title = call.data.get(ATTR_TITLE) 132 | message = call.data.get(ATTR_MESSAGE) 133 | notification_id = call.data.get(ATTR_NOTIFICATION_ID) 134 | 135 | if notification_id is not None: 136 | entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id)) 137 | else: 138 | entity_id = async_generate_entity_id( 139 | ENTITY_ID_FORMAT, DEFAULT_OBJECT_ID, hass=hass 140 | ) 141 | notification_id = entity_id.split(".")[1] 142 | 143 | attr = {} 144 | if title is not None: 145 | try: 146 | title.hass = hass 147 | title = title.async_render() 148 | except TemplateError as ex: 149 | _LOGGER.error("Error rendering title %s: %s", title, ex) 150 | title = title.template 151 | 152 | attr[ATTR_TITLE] = title 153 | 154 | try: 155 | message.hass = hass 156 | message = message.async_render() 157 | except TemplateError as ex: 158 | _LOGGER.error("Error rendering message %s: %s", message, ex) 159 | message = message.template 160 | 161 | attr[ATTR_MESSAGE] = message 162 | 163 | hass.states.async_set(entity_id, STATE, attr) 164 | 165 | # Store notification and fire event 166 | # This will eventually replace state machine storage 167 | dwains_dashboard_notifications[entity_id] = { 168 | ATTR_MESSAGE: message, 169 | ATTR_NOTIFICATION_ID: notification_id, 170 | ATTR_STATUS: STATUS_UNREAD, 171 | ATTR_TITLE: title, 172 | ATTR_CREATED_AT: dt_util.utcnow(), 173 | } 174 | 175 | hass.bus.async_fire(EVENT_DWAINS_dashboard_NOTIFICATIONS_UPDATED) 176 | 177 | @callback 178 | def dismiss_service(call): 179 | """Handle the dismiss notification service call.""" 180 | notification_id = call.data.get(ATTR_NOTIFICATION_ID) 181 | entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id)) 182 | 183 | if entity_id not in dwains_dashboard_notifications: 184 | return 185 | 186 | hass.states.async_remove(entity_id) 187 | 188 | del dwains_dashboard_notifications[entity_id] 189 | hass.bus.async_fire(EVENT_DWAINS_dashboard_NOTIFICATIONS_UPDATED) 190 | 191 | @callback 192 | def mark_read_service(call): 193 | """Handle the mark_read notification service call.""" 194 | notification_id = call.data.get(ATTR_NOTIFICATION_ID) 195 | entity_id = ENTITY_ID_FORMAT.format(slugify(notification_id)) 196 | 197 | if entity_id not in dwains_dashboard_notifications: 198 | _LOGGER.error( 199 | "Marking dwains dashboard_notification read failed: " 200 | "Notification ID %s not found.", 201 | notification_id, 202 | ) 203 | return 204 | 205 | dwains_dashboard_notifications[entity_id][ATTR_STATUS] = STATUS_READ 206 | hass.bus.async_fire(EVENT_DWAINS_dashboard_NOTIFICATIONS_UPDATED) 207 | 208 | hass.services.async_register( 209 | DOMAIN, SERVICE_CREATE, create_service, SCHEMA_SERVICE_CREATE 210 | ) 211 | 212 | hass.services.async_register( 213 | DOMAIN, SERVICE_DISMISS, dismiss_service, SCHEMA_SERVICE_DISMISS 214 | ) 215 | 216 | hass.services.async_register( 217 | DOMAIN, SERVICE_MARK_READ, mark_read_service, SCHEMA_SERVICE_MARK_READ 218 | ) 219 | 220 | websocket_api.async_register_command(hass, websocket_get_notifications) 221 | #End notifications part setup 222 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/process_yaml.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import yaml 3 | import os 4 | import logging 5 | import json 6 | import io 7 | import time 8 | from collections import OrderedDict 9 | import jinja2 10 | import shutil 11 | from concurrent.futures import ThreadPoolExecutor 12 | import asyncio 13 | from aiofiles.os import scandir 14 | 15 | #from homeassistant.util.yaml import Secrets, loader 16 | from annotatedyaml import loader 17 | from annotatedyaml.loader import Secrets 18 | 19 | from homeassistant.exceptions import HomeAssistantError 20 | from homeassistant.core import HomeAssistant 21 | 22 | from .const import DOMAIN, VERSION 23 | 24 | _LOGGER = logging.getLogger(__name__) 25 | 26 | def fromjson(value): 27 | return json.loads(value) 28 | 29 | jinja = jinja2.Environment(loader=jinja2.FileSystemLoader("/")) 30 | 31 | jinja.filters['fromjson'] = fromjson 32 | 33 | dwains_dashboard_more_pages = {} 34 | llgen_config = {} 35 | 36 | def load_yamll(fname, secrets = None, args={}): 37 | try: 38 | process_yaml = False 39 | with open(fname, encoding="utf-8") as f: 40 | if f.readline().lower().startswith(("# dwains_dashboard", "# dwains_theme", "# lovelace_gen", "#dwains_dashboard")): 41 | process_yaml = True 42 | 43 | #_LOGGER.debug(f"load_yamll() Loading YAML: {fname}, process_yaml={process_yaml}") 44 | 45 | if process_yaml: 46 | stream = io.StringIO(jinja.get_template(fname).render({ 47 | **args, 48 | "_dd_more_pages": dwains_dashboard_more_pages, 49 | "_global": llgen_config 50 | })) 51 | stream.name = fname 52 | return loader.yaml.load(stream, Loader=lambda _stream: loader.PythonSafeLoader(_stream, secrets)) or OrderedDict() 53 | else: 54 | with open(fname, encoding="utf-8") as config_file: 55 | data = loader.yaml.load(config_file, Loader=lambda stream: loader.PythonSafeLoader(stream, secrets)) or OrderedDict() 56 | #_LOGGER.warning(f"load_yamll() DATA: {data}") 57 | return data 58 | 59 | except loader.yaml.YAMLError as exc: 60 | _LOGGER.error(f"YAMLError: {str(exc)}") 61 | raise HomeAssistantError(exc) 62 | except UnicodeDecodeError as exc: 63 | _LOGGER.error("Unicode Error :: Unable to read file %s: %s", fname, exc) 64 | raise HomeAssistantError(exc) 65 | 66 | 67 | def _include_yaml(ldr, node): 68 | args = {} 69 | if isinstance(node.value, str): 70 | fn = node.value 71 | else: 72 | fn, args, *_ = ldr.construct_sequence(node) 73 | fname = os.path.abspath(os.path.join(os.path.dirname(ldr.name), fn)) 74 | try: 75 | return loader._add_reference(load_yamll(fname, ldr.secrets, args=args), ldr, node) 76 | except FileNotFoundError as exc: 77 | _LOGGER.error("Unable to include file %s: %s", fname, exc); 78 | raise HomeAssistantError(exc) 79 | 80 | loader.load_yaml = load_yamll 81 | loader.PythonSafeLoader.add_constructor("!include", _include_yaml) 82 | 83 | def compose_node(self, parent, index): 84 | if self.check_event(yaml.events.AliasEvent): 85 | event = self.get_event() 86 | anchor = event.anchor 87 | if anchor not in self.anchors: 88 | raise yaml.composer.ComposerError(None, None, "found undefined alias %r" 89 | % anchor, event.start_mark) 90 | return self.anchors[anchor] 91 | event = self.peek_event() 92 | anchor = event.anchor 93 | self.descend_resolver(parent, index) 94 | if self.check_event(yaml.events.ScalarEvent): 95 | node = self.compose_scalar_node(anchor) 96 | elif self.check_event(yaml.events.SequenceStartEvent): 97 | node = self.compose_sequence_node(anchor) 98 | elif self.check_event(yaml.events.MappingStartEvent): 99 | node = self.compose_mapping_node(anchor) 100 | self.ascend_resolver() 101 | return node 102 | 103 | yaml.composer.Composer.compose_node = compose_node 104 | 105 | 106 | async def process_yaml(hass: HomeAssistant, config_entry): 107 | """Process all YAML files for Dwains Dashboard.""" 108 | #_LOGGER.warning('Start of function to process all yaml files!') 109 | 110 | # Check for HKI installation 111 | if os.path.exists(hass.config.path("hki-user/config")): 112 | #_LOGGER.warning("HKI Installed!") 113 | for fname in loader._find_files(hass.config.path("hki-user/config"), "*.yaml"): 114 | loaded_yaml = load_yamll(fname) 115 | if isinstance(loaded_yaml, dict): 116 | llgen_config.update(loaded_yaml) 117 | 118 | if os.path.exists(hass.config.path("dwains-dashboard/configs")): 119 | if os.path.isdir(hass.config.path("dwains-dashboard/configs/more_pages")): 120 | #for subdir in os.listdir(hass.config.path("dwains-dashboard/configs/more_pages")): 121 | more_pages_path = hass.config.path("dwains-dashboard/configs/more_pages") 122 | subdirs = await hass.async_add_executor_job(os.listdir, more_pages_path) 123 | for subdir in subdirs: 124 | #Lets check if there is a page.yaml in the more_pages folder 125 | if os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml")): 126 | # Page.yaml exists now check if there is a config.yaml otherwise create it 127 | if not os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml")): 128 | #_LOGGER.warning(f"process_yaml() config.yaml does not exist, {subdir}") 129 | #with open(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml"), 'w') as f: 130 | file_content = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml"), "w") 131 | with file_content as f: 132 | page_config = OrderedDict() 133 | page_config.update({ 134 | "name": subdir, 135 | "icon": "mdi:puzzle" 136 | }) 137 | yaml.safe_dump(page_config, f, default_flow_style=False) 138 | dwains_dashboard_more_pages[subdir] = { 139 | "name": subdir, 140 | "icon": "mdi:puzzle", 141 | "path": "dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml", 142 | } 143 | else: 144 | #_LOGGER.warning(f"process_yaml() config.yaml exists, {subdir}") 145 | try: 146 | #with open(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml")) as f: 147 | data = await hass.async_add_executor_job(open, hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml"), "r") 148 | with data as f: 149 | filecontent = yaml.safe_load(f) 150 | 151 | #_LOGGER.warning(f"FILE CONTENT: {filecontent}") 152 | if "name" in filecontent and "icon" in filecontent: 153 | dwains_dashboard_more_pages[subdir] = { 154 | "name": filecontent["name"], 155 | "icon": filecontent["icon"], 156 | "path": "dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml", 157 | } 158 | else: 159 | _LOGGER.warning(f"Invalid config.yaml in {subdir}: Missing 'name' or 'icon'") 160 | except Exception as e: 161 | _LOGGER.error(f"Failed to read config.yaml in {subdir}: {e}") 162 | 163 | hass.bus.async_fire("dwains_dashboard_reload") 164 | 165 | async def handle_reload(call): 166 | #Service call to reload Dwains Theme config 167 | _LOGGER.warning("Reload Dwains Dashboard Configuration") 168 | 169 | await reload_configuration(hass) 170 | 171 | # Register service dwains_dashboard.reload 172 | hass.services.async_register(DOMAIN, "reload", handle_reload) 173 | 174 | 175 | 176 | async def reload_configuration(hass): 177 | _LOGGER.warning('Reload YAML configuration files...!') 178 | 179 | if os.path.exists(hass.config.path("dwains-dashboard/configs")): 180 | if os.path.isdir(hass.config.path("dwains-dashboard/configs/more_pages")): 181 | #for subdir in os.listdir(hass.config.path("dwains-dashboard/configs/more_pages")): 182 | more_pages_path = hass.config.path("dwains-dashboard/configs/more_pages") 183 | subdirs = await hass.async_add_executor_job(os.listdir, more_pages_path) 184 | for subdir in subdirs: 185 | #Lets check if there is a page.yaml in the more_pages folder 186 | if os.path.exists(hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml")): 187 | page_config = hass.config.path("dwains-dashboard/configs/more_pages/"+subdir+"/config.yaml") 188 | #Page.yaml exists now check if there is a config.yaml otherwise create it 189 | if not os.path.exists(page_config): 190 | data = await hass.async_add_executor_job(open, page_config, "w") 191 | with data as f: 192 | page_config = OrderedDict() 193 | page_config.update({ 194 | "name": subdir, 195 | "icon": "mdi:puzzle" 196 | }) 197 | yaml.safe_dump(page_config, f, default_flow_style=False) 198 | dwains_dashboard_more_pages[subdir] = { 199 | "name": subdir, 200 | "icon": "mdi:puzzle", 201 | "path": "dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml", 202 | } 203 | else: 204 | data = await hass.async_add_executor_job(open, page_config, "r") 205 | with data as f: 206 | filecontent = yaml.safe_load(f) 207 | dwains_dashboard_more_pages[subdir] = { 208 | "name": filecontent["name"], 209 | "icon": filecontent["icon"], 210 | "path": "dwains-dashboard/configs/more_pages/"+subdir+"/page.yaml", 211 | } 212 | 213 | hass.bus.async_fire("dwains_dashboard_reload") -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/sensor.py: -------------------------------------------------------------------------------- 1 | from homeassistant.helpers.entity import Entity 2 | from datetime import datetime, timedelta 3 | from homeassistant.util import Throttle 4 | 5 | from .const import DOMAIN, VERSION 6 | 7 | import logging 8 | 9 | import asyncio 10 | import aiohttp 11 | import async_timeout 12 | import json 13 | from homeassistant.helpers.aiohttp_client import async_get_clientsession 14 | 15 | _LOGGER = logging.getLogger(__name__) 16 | _RESOURCE = "https://dwains-dashboard.dwainscheeren.nl/version?v="+VERSION 17 | 18 | MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=800) 19 | 20 | async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): 21 | """Setup sensor platform.""" 22 | #_LOGGER.error("async_setup_platform called") 23 | async_add_entities([LatestVersionSensor()]) 24 | 25 | 26 | async def async_setup_entry(hass, config_entry, async_add_devices): 27 | """Setup sensor platform.""" 28 | #_LOGGER.error("async_setup_entry called") 29 | 30 | data = LatestVersion(hass) 31 | async_add_devices([LatestVersionSensor(data)]) 32 | 33 | 34 | class LatestVersionSensor(Entity): 35 | """Latest version sensor.""" 36 | 37 | def __init__(self, data): 38 | """Initialize the sensor.""" 39 | self._state = None 40 | self.data = data 41 | 42 | @property 43 | def unique_id(self): 44 | """Return a unique ID to use for this sensor.""" 45 | return ( 46 | "dwains-dashboard-latest-version" 47 | ) 48 | 49 | @property 50 | def name(self): 51 | """Return the name of the sensor.""" 52 | return "Dwains Dashboard Latest version" 53 | 54 | @property 55 | def icon(self): 56 | """Return the icon of the sensor.""" 57 | return "mdi:alpha-d-box" 58 | 59 | @property 60 | def state(self): 61 | """Return the state of the sensor.""" 62 | return self._state 63 | 64 | @property 65 | def unit_of_measurement(self): 66 | """Return the unit of measurement.""" 67 | return "latest version" 68 | 69 | # def update(self): 70 | # """Fetch new state data for the sensor. 71 | # This is the only method that should fetch new data for Home Assistant. 72 | # """ 73 | # self._state = self.hass.data[DOMAIN]['latest_version'] 74 | 75 | async def async_update(self): 76 | await self.data.update() 77 | self._state = self.hass.data[DOMAIN]['latest_version'] 78 | 79 | class LatestVersion: 80 | 81 | def __init__(self, hass): 82 | self._hass = hass 83 | 84 | @Throttle(MIN_TIME_BETWEEN_UPDATES) 85 | async def update(self): 86 | 87 | session = async_get_clientsession(self._hass) 88 | 89 | try: 90 | with async_timeout.timeout(10): 91 | response = await session.get(_RESOURCE) 92 | result = await response.read() 93 | data = json.loads(result) 94 | if "latest_version" in data: 95 | #_LOGGER.error(data) 96 | self._hass.data[DOMAIN]['latest_version'] = json.loads(result)["latest_version"] 97 | except ValueError as err: 98 | _LOGGER.error("Dwains Dashboard version check failed %s", err.args) 99 | except (asyncio.TimeoutError, aiohttp.ClientError) as err: 100 | _LOGGER.error("Dwains Dashboard version check failed %s", repr(err)) -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/services.yaml: -------------------------------------------------------------------------------- 1 | reload: 2 | description: Reload dashboard configuration from Dwains dashboard 3 | 4 | notification_create: 5 | description: Show a notification in the frontend. 6 | fields: 7 | message: 8 | description: Message body of the notification. [Templates accepted] 9 | example: Dishwasher is done! :D 10 | notification_id: 11 | description: Target ID of the notification, will replace a notification with the same Id. [Optional] 12 | example: 1234 13 | 14 | notification_dismiss: 15 | description: Remove a notification from the frontend. 16 | fields: 17 | notification_id: 18 | description: Target ID of the notification, which should be removed. [Required] 19 | example: 1234 20 | 21 | notification_mark_read: 22 | description: Mark a notification read. 23 | fields: 24 | notification_id: 25 | description: Target ID of the notification, which should be mark read. [Required] 26 | example: 1234 -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/translations/bg.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Главно табло", 3 | "config": { 4 | "step": { 5 | "init": { 6 | "title": "Настройки на таблото", 7 | "description": "", 8 | "data": { 9 | "sidepanel_icon": "Икона в панела", 10 | "sidepanel_title": "Заглавие в панела" 11 | } 12 | } 13 | }, 14 | "abort": { 15 | "single_instance_allowed": "Само една конфигурациоя на таблото е разрешена." 16 | } 17 | }, 18 | "options": { 19 | "step": { 20 | "init": { 21 | "title": "Настройки на таблото", 22 | "description": "", 23 | "data": { 24 | "sidepanel_icon": "Икона в панела", 25 | "sidepanel_title": "Заглавие в панела" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Dwains Dashboard", 3 | "config": { 4 | "step": { 5 | "init": { 6 | "title": "Dwains Dashboard Settings", 7 | "description": "", 8 | "data": { 9 | "sidepanel_icon": "Side panel icon", 10 | "sidepanel_title": "Side panel title" 11 | } 12 | } 13 | }, 14 | "abort": { 15 | "single_instance_allowed": "Only a single configuration of Dwains Dashboard is allowed." 16 | } 17 | }, 18 | "options": { 19 | "step": { 20 | "init": { 21 | "title": "Dwains Dashboard Settings", 22 | "description": "", 23 | "data": { 24 | "sidepanel_icon": "Side panel icon", 25 | "sidepanel_title": "Side panel title" 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/translations/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Dwains panel", 3 | "config": { 4 | "step": { 5 | "init": { 6 | "title": "Ustawienia Dwains panel", 7 | "description": "", 8 | "data": { 9 | "sidepanel_icon": "Ikona na panelu bocznym", 10 | "sidepanel_title": "Tytuł na panelu bocznym" 11 | } 12 | } 13 | }, 14 | "abort": { 15 | "single_instance_allowed": "Tylko pojedyńcza konfiguracja Dwains panel jest dozwolona." 16 | } 17 | }, 18 | "options": { 19 | "step": { 20 | "init": { 21 | "title": "Ustawienia Dwains panel", 22 | "description": "", 23 | "data": { 24 | "sidepanel_icon": "Ikona na panelu bocznym", 25 | "sidepanel_title": "Tytuł na panelu bocznym" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /custom_components/dwains_dashboard/translations/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Dwains 仪表板", 3 | "config": { 4 | "step": { 5 | "init": { 6 | "title": "Dwains 仪表板设置", 7 | "description": "", 8 | "data": { 9 | "sidepanel_icon": "侧边栏图标", 10 | "sidepanel_title": "侧边栏标题" 11 | } 12 | } 13 | }, 14 | "abort": { 15 | "single_instance_allowed": "只允许一个 Dwains 仪表板的配置。" 16 | } 17 | }, 18 | "options": { 19 | "step": { 20 | "init": { 21 | "title": "Dwains 仪表板设置", 22 | "description": "", 23 | "data": { 24 | "sidepanel_icon": "侧边栏图标", 25 | "sidepanel_title": "侧边栏标题" 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dwains Dashboard", 3 | "render_readme": true, 4 | "homeassistant": "2025.4.0" 5 | } --------------------------------------------------------------------------------