Add new wizard + allow installing firmware over webserial (#1887)

This commit is contained in:
Paulus Schoutsen 2021-06-11 15:49:05 -07:00 committed by Jesse Hills
parent 575badc690
commit 9bb64315f3
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
24 changed files with 98 additions and 2560 deletions

View file

@ -9,6 +9,7 @@ import json
import logging import logging
import multiprocessing import multiprocessing
import os import os
import secrets
import shutil import shutil
import subprocess import subprocess
import threading import threading
@ -44,6 +45,8 @@ from esphome.zeroconf import DashboardStatus, Zeroconf
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ENV_DEV = "ESPHOME_DASHBOARD_DEV"
class DashboardSettings: class DashboardSettings:
def __init__(self): def __init__(self):
@ -111,6 +114,7 @@ def template_args():
docs_link = "https://next.esphome.io/" docs_link = "https://next.esphome.io/"
else: else:
docs_link = "https://www.esphome.io/" docs_link = "https://www.esphome.io/"
return { return {
"version": version, "version": version,
"docs_link": docs_link, "docs_link": docs_link,
@ -349,6 +353,7 @@ class SerialPortRequestHandler(BaseHandler):
data.append({"port": port.path, "desc": desc}) data.append({"port": port.path, "desc": desc})
data.append({"port": "OTA", "desc": "Over-The-Air"}) data.append({"port": "OTA", "desc": "Over-The-Air"})
data.sort(key=lambda x: x["port"], reverse=True) data.sort(key=lambda x: x["port"], reverse=True)
self.set_header("content-type", "application/json")
self.write(json.dumps(data)) self.write(json.dumps(data))
@ -358,11 +363,15 @@ class WizardRequestHandler(BaseHandler):
from esphome import wizard from esphome import wizard
kwargs = { kwargs = {
k: "".join(x.decode() for x in v) for k, v in self.request.arguments.items() k: "".join(x.decode() for x in v)
for k, v in self.request.arguments.items()
if k in ("name", "platform", "board", "ssid", "psk", "password")
} }
kwargs["ota_password"] = secrets.token_hex(16)
destination = settings.rel_path(kwargs["name"] + ".yaml") destination = settings.rel_path(kwargs["name"] + ".yaml")
wizard.wizard_write(path=destination, **kwargs) wizard.wizard_write(path=destination, **kwargs)
self.redirect("./?begin=True") self.set_status(200)
self.finish()
class DownloadBinaryRequestHandler(BaseHandler): class DownloadBinaryRequestHandler(BaseHandler):
@ -473,7 +482,7 @@ class MainRequestHandler(BaseHandler):
entries = _list_dashboard_entries() entries = _list_dashboard_entries()
self.render( self.render(
"templates/index.html", get_template_path("index"),
entries=entries, entries=entries,
begin=begin, begin=begin,
**template_args(), **template_args(),
@ -560,6 +569,7 @@ class PingRequestHandler(BaseHandler):
@authenticated @authenticated
def get(self): def get(self):
PING_REQUEST.set() PING_REQUEST.set()
self.set_header("content-type", "application/json")
self.write(json.dumps(PING_RESULT)) self.write(json.dumps(PING_RESULT))
@ -567,6 +577,21 @@ def is_allowed(configuration):
return os.path.sep not in configuration return os.path.sep not in configuration
class InfoRequestHandler(BaseHandler):
@authenticated
@bind_config
def get(self, configuration=None):
yaml_path = settings.rel_path(configuration)
all_yaml_files = settings.list_yaml_files()
if yaml_path not in all_yaml_files:
self.set_status(404)
return
self.set_header("content-type", "application/json")
self.write(DashboardEntry(yaml_path).storage.to_json())
class EditRequestHandler(BaseHandler): class EditRequestHandler(BaseHandler):
@authenticated @authenticated
@bind_config @bind_config
@ -633,7 +658,7 @@ class LoginHandler(BaseHandler):
def render_login_page(self, error=None): def render_login_page(self, error=None):
self.render( self.render(
"templates/login.html", get_template_path("login"),
error=error, error=error,
hassio=settings.using_hassio_auth, hassio=settings.using_hassio_auth,
has_username=bool(settings.username), has_username=bool(settings.username),
@ -694,19 +719,44 @@ class LogoutHandler(BaseHandler):
_STATIC_FILE_HASHES = {} _STATIC_FILE_HASHES = {}
def get_base_frontend_path():
if ENV_DEV not in os.environ:
import esphome_dashboard
return esphome_dashboard.where()
static_path = os.environ[ENV_DEV]
if not static_path.endswith("/"):
static_path += "/"
# This path can be relative, so resolve against the root or else templates don't work
return os.path.abspath(os.path.join(os.getcwd(), static_path, "esphome_dashboard"))
def get_template_path(template_name):
return os.path.join(get_base_frontend_path(), f"{template_name}.template.html")
def get_static_path(*args):
return os.path.join(get_base_frontend_path(), "static", *args)
def get_static_file_url(name): def get_static_file_url(name):
static_path = os.path.join(os.path.dirname(__file__), "static") # Module imports can't deduplicate if stuff added to url
if name == "js/esphome/index.js":
return f"./static/{name}"
if name in _STATIC_FILE_HASHES: if name in _STATIC_FILE_HASHES:
hash_ = _STATIC_FILE_HASHES[name] hash_ = _STATIC_FILE_HASHES[name]
else: else:
path = os.path.join(static_path, name) path = get_static_path(name)
with open(path, "rb") as f_handle: with open(path, "rb") as f_handle:
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
_STATIC_FILE_HASHES[name] = hash_ _STATIC_FILE_HASHES[name] = hash_
return f"./static/{name}?hash={hash_}" return f"./static/{name}?hash={hash_}"
def make_app(debug=False): def make_app(debug=get_bool_env(ENV_DEV)):
def log_function(handler): def log_function(handler):
if handler.get_status() < 400: if handler.get_status() < 400:
log_method = access_log.info log_method = access_log.info
@ -736,7 +786,6 @@ def make_app(debug=False):
"Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"
) )
static_path = os.path.join(os.path.dirname(__file__), "static")
app_settings = { app_settings = {
"debug": debug, "debug": debug,
"cookie_secret": settings.cookie_secret, "cookie_secret": settings.cookie_secret,
@ -758,6 +807,7 @@ def make_app(debug=False):
(rel + "vscode", EsphomeVscodeHandler), (rel + "vscode", EsphomeVscodeHandler),
(rel + "ace", EsphomeAceEditorHandler), (rel + "ace", EsphomeAceEditorHandler),
(rel + "update-all", EsphomeUpdateAllHandler), (rel + "update-all", EsphomeUpdateAllHandler),
(rel + "info", InfoRequestHandler),
(rel + "edit", EditRequestHandler), (rel + "edit", EditRequestHandler),
(rel + "download.bin", DownloadBinaryRequestHandler), (rel + "download.bin", DownloadBinaryRequestHandler),
(rel + "serial-ports", SerialPortRequestHandler), (rel + "serial-ports", SerialPortRequestHandler),
@ -765,7 +815,7 @@ def make_app(debug=False):
(rel + "delete", DeleteRequestHandler), (rel + "delete", DeleteRequestHandler),
(rel + "undo-delete", UndoDeleteRequestHandler), (rel + "undo-delete", UndoDeleteRequestHandler),
(rel + "wizard.html", WizardRequestHandler), (rel + "wizard.html", WizardRequestHandler),
(rel + r"static/(.*)", StaticFileHandler, {"path": static_path}), (rel + r"static/(.*)", StaticFileHandler, {"path": get_static_path()}),
], ],
**app_settings, **app_settings,
) )

View file

@ -1,398 +0,0 @@
/* Base */
:root {
/* Colors */
--primary-bg-color: #fafafa;
--alert-standard-color: #666666;
--alert-standard-color-bg: #e6e6e6;
--alert-info-color: #00539f;
--alert-info-color-bg: #E6EEF5;
--alert-success-color: #4CAF50;
--alert-success-color-bg: #EDF7EE;
--alert-warning-color: #FF9800;
--alert-warning-color-bg: #FFF5E6;
--alert-error-color: #D93025;
--alert-error-color-bg: #FAEFEB;
}
body {
display: flex;
min-height: 100vh;
flex-direction: column;
background-color: var(--primary-bg-color);
}
/* Layout */
.valign-wrapper {
position: absolute;
width:100vw;
height:100vh;
}
.valign {
width: 100%;
}
main {
flex: 1 0 auto;
}
/* Alerts & Errors */
.alert {
width: 100%;
margin: 10px auto;
padding: 10px;
border-radius: 2px;
border-left-width: 4px;
border-left-style: solid;
}
.alert .title {
font-weight: bold;
}
.alert .title::after {
content: "\A";
white-space: pre;
}
.alert.alert-error {
color: var(--alert-error-color);
border-left-color: var(--alert-error-color);
background-color: var(--alert-error-color-bg);
}
.card.card-error, .card.status-offline {
border-top: 4px solid var(--alert-error-color);
}
.card.status-online {
border-top: 4px solid var(--alert-success-color);
}
.card.status-not-responding {
border-top: 4px solid var(--alert-warning-color);
}
.card.status-unknown {
border-top: 4px solid var(--alert-standard-color);
}
/* Login Page */
#login-page .row.no-bottom-margin {
margin-bottom: 0 !important;
}
#login-page .logo {
display: block;
width: auto;
max-width: 300px;
margin-left: auto;
margin-right: auto;
}
#login-page .input-field input:focus + label {
color: #000;
}
#login-page .input-field input:focus {
border-bottom: 1px solid #000;
box-shadow: 0 1px 0 0 #000;
}
#login-page .input-field .prefix.active {
color: #000;
}
#login-page .version-number {
display: block;
text-align: center;
margin-bottom: 20px;;
color:#808080;
font-size: 12px;
}
#login-page footer {
color: #757575;
font-size: 12px;
}
#login-page footer a {
color: #424242;
}
#login-page footer p {
-webkit-margin-before: 0px;
margin-block-start: 0px;
-webkit-margin-after: 5px;
margin-block-end: 5px;
}
#login-page footer p:last-child {
-webkit-margin-after: 0px;
margin-block-end: 0px;
}
/* Dashboard */
.logo-wrapper {
height: 64px;
height: 100%;
width: 0;
margin-left: 24px;
}
.logo {
width: auto;
height: 48px;
margin: 8px 0;
}
@media only screen and (max-width: 601px) {
.logo {
height: 38px;
margin: 9px 0;
}
}
.nav-icons {
margin-right: 24px;
}
.nav-icons i {
color: black;
}
nav {
height: auto;
line-height: normal;
}
.select-port-container {
margin-top: 8px;
margin-right: 10px;
width: 350px;
}
.serial-port-select {
margin-top: 8px;
margin-right: 10px;
width: 350px;
}
.serial-port-select .select-dropdown {
color: black;
}
.serial-port-select .select-dropdown:focus {
border-bottom: 1px solid #607d8b !important;
}
.serial-port-select .caret {
fill: black;
}
.serial-port-select .dropdown-content li>span {
color: black;
}
#nav-dropdown li a, .node-dropdown li a {
color: black;
}
main .container {
margin-top: 20px;
margin-bottom: 20px;
width: 90%;
max-width: 1920px;
}
#nodes .card-content {
height: calc(100% - 47px);
}
#nodes .card-content, #nodes .card-action {
padding: 12px;
}
#nodes .grid-1-col {
display: grid;
grid-template-columns: 1fr;
}
#nodes .grid-2-col {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 1.5rem;
}
#nodes .grid-3-col {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-column-gap: 1.5rem;
}
@media only screen and (max-width: 1100px) {
#nodes .grid-3-col {
grid-template-columns: 1fr 1fr;
grid-column-gap: 1.5rem;
}
}
@media only screen and (max-width: 750px) {
#nodes .grid-2-col {
grid-template-columns: 1fr;
grid-column-gap: 0;
}
#nodes .grid-3-col {
grid-template-columns: 1fr;
grid-column-gap: 0;
}
}
i.node-update-avaliable {
color:#3f51b5;
}
i.node-webserver {
color:#039be5;
}
.node-config-path {
margin-top: -8px;
margin-bottom: 8px;
font-size: 14px;
}
.node-card-comment {
color: #444;
font-style: italic;
}
.card-action a, .card-dropdown-action a {
cursor: pointer;
}
.tooltipped {
cursor: help;
}
#js-loading-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.editor {
margin-top: 0;
margin-bottom: 0;
border-radius: 3px;
height: calc(100% - 56px);
}
.inlinecode {
box-sizing: border-box;
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: rgba(27,31,35,0.05);
border-radius: 3px;
font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace;
}
.log {
height: 100%;
max-height: calc(100% - 56px);
background-color: #1c1c1c;
margin-top: 0;
margin-bottom: 0;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 12px;
padding: 16px;
overflow: auto;
line-height: 1.45;
border-radius: 3px;
white-space: pre-wrap;
overflow-wrap: break-word;
color: #DDD;
}
.log-bold { font-weight: bold; }
.log-italic { font-style: italic; }
.log-underline { text-decoration: underline; }
.log-strikethrough { text-decoration: line-through; }
.log-underline.log-strikethrough { text-decoration: underline line-through; }
.log-secret {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.log-secret-redacted {
opacity: 0;
width: 1px;
font-size: 1px;
}
.log-fg-black { color: rgb(128,128,128); }
.log-fg-red { color: rgb(255,0,0); }
.log-fg-green { color: rgb(0,255,0); }
.log-fg-yellow { color: rgb(255,255,0); }
.log-fg-blue { color: rgb(0,0,255); }
.log-fg-magenta { color: rgb(255,0,255); }
.log-fg-cyan { color: rgb(0,255,255); }
.log-fg-white { color: rgb(187,187,187); }
.log-bg-black { background-color: rgb(0,0,0); }
.log-bg-red { background-color: rgb(255,0,0); }
.log-bg-green { background-color: rgb(0,255,0); }
.log-bg-yellow { background-color: rgb(255,255,0); }
.log-bg-blue { background-color: rgb(0,0,255); }
.log-bg-magenta { background-color: rgb(255,0,255); }
.log-bg-cyan { background-color: rgb(0,255,255); }
.log-bg-white { background-color: rgb(255,255,255); }
ul.browser-default {
padding-left: 30px;
margin-top: 10px;
margin-bottom: 15px;
}
ul.browser-default li {
list-style-type: initial;
}
ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before {
background-color: #3f51b5 !important;
}
.select-action {
width: auto !important;
height: auto !important;
white-space: nowrap;
}
.modal {
width: 95%;
max-height: 90%;
height: 85% !important;
}
.page-footer {
display: flex;
align-items: center;
min-height: 50px;
padding-top: 0;
color: grey;
}
.page-footer a {
color: #afafaf;
}
@media only screen and (max-width: 992px) {
.page-footer .left, .page-footer .right {
width: 100%;
text-align: center;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,12 +0,0 @@
The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts:
```html
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
```
Read more in our full usage guide:
http://google.github.io/material-design-icons/#icon-font-for-the-web
Source:
https://github.com/google/material-design-icons

View file

@ -1,34 +0,0 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(MaterialIcons-Regular.woff2) format('woff2'),
url(MaterialIcons-Regular.woff) format('woff');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,7 +0,0 @@
define("ace/theme/dreamweaver", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { t.isDark = !1, t.cssClass = "ace-dreamweaver", t.cssText = '.ace-dreamweaver .ace_gutter {background: #e8e8e8;color: #333;}.ace-dreamweaver .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-dreamweaver {background-color: #FFFFFF;color: black;}.ace-dreamweaver .ace_fold {background-color: #757AD8;}.ace-dreamweaver .ace_cursor {color: black;}.ace-dreamweaver .ace_invisible {color: rgb(191, 191, 191);}.ace-dreamweaver .ace_storage,.ace-dreamweaver .ace_keyword {color: blue;}.ace-dreamweaver .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-dreamweaver .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-dreamweaver .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-dreamweaver .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-dreamweaver .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_support.ace_type,.ace-dreamweaver .ace_support.ace_class {color: #009;}.ace-dreamweaver .ace_support.ace_php_tag {color: #f00;}.ace-dreamweaver .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-dreamweaver .ace_string {color: #00F;}.ace-dreamweaver .ace_comment {color: rgb(76, 136, 107);}.ace-dreamweaver .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-dreamweaver .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-dreamweaver .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-dreamweaver .ace_variable {color: #06F}.ace-dreamweaver .ace_xml-pe {color: rgb(104, 104, 91);}.ace-dreamweaver .ace_entity.ace_name.ace_function {color: #00F;}.ace-dreamweaver .ace_heading {color: rgb(12, 7, 255);}.ace-dreamweaver .ace_list {color:rgb(185, 6, 144);}.ace-dreamweaver .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-dreamweaver .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-dreamweaver .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-dreamweaver .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-dreamweaver .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-dreamweaver .ace_gutter-active-line {background-color : #DCDCDC;}.ace-dreamweaver .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-dreamweaver .ace_meta.ace_tag {color:#009;}.ace-dreamweaver .ace_meta.ace_tag.ace_anchor {color:#060;}.ace-dreamweaver .ace_meta.ace_tag.ace_form {color:#F90;}.ace-dreamweaver .ace_meta.ace_tag.ace_image {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_script {color:#900;}.ace-dreamweaver .ace_meta.ace_tag.ace_style {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_table {color:#099;}.ace-dreamweaver .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-dreamweaver .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}'; var r = e("../lib/dom"); r.importCssString(t.cssText, t.cssClass) }); (function () {
window.require(["ace/theme/dreamweaver"], function (m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,678 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - ESPHome</title>
<link rel="shortcut icon" href="{{ get_static_file_url('images/favicon.ico') }}">
<link rel="stylesheet" href="{{ get_static_file_url('fonts/material-icons/material-icons.css') }}">
<link rel="stylesheet" href="{{ get_static_file_url('css/vendor/materialize/materialize.min.css') }}">
<link rel="stylesheet" href="{{ get_static_file_url('css/vendor/materialize-stepper/materialize-stepper.min.css') }}">
<link rel="stylesheet" href="{{ get_static_file_url('css/esphome.css') }}">
<script src="{{ get_static_file_url('js/vendor/jquery/jquery.min.js') }}"></script>
<script src="{{ get_static_file_url('js/vendor/jquery-ui/jquery-ui.min.js') }}"></script>
<script src="{{ get_static_file_url('js/vendor/jquery-validate/jquery.validate.min.js') }}"></script>
<script src="{{ get_static_file_url('js/vendor/materialize/materialize.min.js') }}"></script>
<script src="{{ get_static_file_url('js/vendor/materialize-stepper/materialize-stepper.min.js') }}"></script>
{% if streamer_mode %}
<style>
.log-secret {
visibility: hidden !important;
}
</style>
{% end %}
</head>
<body>
<div class="navbar-fixed">
<nav class="grey lighten-2">
<div class="nav-wrapper">
<a href="#" class="black-text logo-wrapper">
<img src="https://esphome.io/_static/logo-text.svg" alt="ESPHome Logo" class="logo">
</a>
<ul class="nav-icons right">
<li>
<a class="dropdown-trigger" href="#!" data-target="nav-dropdown"><i class="material-icons">more_vert</i></a>
</li>
</ul>
<div class="serial-port-select right" id="js-serial-port-select">
<select></select>
</div>
</div>
<ul id="nav-dropdown" class="select-action dropdown-content">
<li><a data-action="update-all" data-filename="{{ escape(config_dir) }}">Update All</a></li>
<li><a data-action="edit" data-filename="secrets.yaml">Secrets Editor</a></li>
<li class="divider"></li>
{% if login_enabled %}
<li><a href="{{ relative_url }}logout">Logout</a></li>
{% end %}
</ul>
</nav>
</div>
<main>
<div id="nodes" class="container">
<div id="grid" class="grid">
{% for i, entry in enumerate(entries) %}
<div class="card status-unknown" data-node-name="{{ entry.name }}" data-filename="{{ entry.filename }}">
<div class="card-content black-text">
<span class="card-title">
<span class="node-name">{{ escape(entry.name) }}</span>
<i class="material-icons right dropdown-trigger" data-target="dropdown-{{ i }}">more_vert</i>
{% if 'web_server' in entry.loaded_integrations %}
<a href="http://{{ escape(entry.address) }}" target="_blank"><i
class="material-icons node-webserver right tooltipped" data-position="left"
data-tooltip="Open Node Web Server Interface">launch</i></a>
{% end %}
{% if entry.update_available %}
<i class="material-icons node-update-avaliable right tooltipped" data-position="left"
data-tooltip="Update Available: {{ entry.update_old }} &#x27A1;&#xFE0F;{{ entry.update_new }}">system_update</i>
{% end %}
</span>
<div class="node-config-path">
Filename:
<code class="inlinecode tooltipped" data-position="bottom"
data-tooltip="Full Path: <code class=&quot;inlinecode&quot;>{{ escape(entry.path) }}</code>">
{{ escape(entry.filename) }}
</code>
</div>
{% if entry.comment %}
<div class="node-card-comment">
{{ escape(entry.comment) }}
</div>
{% end %}
</div>
<div class="card-action">
<a data-action="edit" data-filename="{{ entry.filename }}">Edit</a>
<a data-action="validate" data-filename="{{ entry.filename }}">Validate</a>
<a data-action="upload" data-filename="{{ entry.filename }}">Upload</a>
<a data-action="logs" data-filename="{{ entry.filename }}">Logs</a>
</div>
<ul id="dropdown-{{ i }}" class="select-action dropdown-content card-dropdown-action node-dropdown">
<li><a data-action="clean-mqtt" data-filename="{{ entry.filename }}">Clean MQTT</a></li>
<li><a data-action="clean" data-filename="{{ entry.filename }}">Clean Build Files</a></li>
<li><a data-action="compile" data-filename="{{ entry.filename }}">Compile</a></li>
<li class="divider"></li>
<li><a data-action="delete" data-filename="{{ entry.filename }}">Delete</a></li>
</ul>
</div>
{% end %}
</div>
</div>
{% if len(entries) == 0 %}
<div class="center">
<h5>Welcome to ESPHome</h5>
<p>It looks like you don't yet have any Nodes configured.</p>
<p>Click on the pulsating button at the bottom right of the page to add a Node.</p>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const addNodeButton = document.querySelector("#js-wizard");
addNodeButton.classList.add("pulse");
});
</script>
{% end %}
<!-- Config Editor Modal -->
<div id="js-editor-modal" class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
<h4>Editing: <code id="js-node-filename" class="inlinecode"></code></h4>
<div id="js-loading-indicator">
<div class="preloader-wrapper big active">
<div class="spinner-layer spinner-blue-only">
<div class="circle-clipper left">
<div class="circle"></div>
</div>
<div class="gap-patch">
<div class="circle"></div>
</div>
<div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
</div>
</div>
<div id="js-editor-area" class="editor"></div>
</div>
<div class="modal-footer">
<a class="waves-effect waves-green btn-flat" data-action="save">Save</a>
<a class="modal-close waves-effect waves-green btn-flat" data-action="upload">Upload</a>
<a class="modal-close waves-effect waves-green btn-flat" data-action="close">Close</a>
</div>
</div>
<!-- Upload Modal -->
<div id="js-upload-modal" class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
<h4>Compiling & Uploading: <code id="js-node-filename" class="inlinecode"></code></h4>
<pre id="js-log-area" class="log"></pre>
</div>
<div class="modal-footer">
<a href="https://esphome.io/guides/faq.html#i-can-t-get-flashing-over-usb-to-work" target="_blank"
rel="noreferrer">
<i class="material-icons">help_outline</i>
</a>
<a class="modal-close waves-effect waves-green btn-flat" data-action="edit">Edit</a>
<a class="modal-close waves-effect waves-green btn-flat" data-action="stop-logs">Stop</a>
<div class="btn-flat"><i class="material-icons dropdown-trigger"
data-target="dropdown-upload-actions">more_vert</i>
</div>
</div>
<ul id="dropdown-upload-actions" class="select-action dropdown-content card-dropdown-action">
<li>
<a class="modal-close waves-effect waves-green btn-flat disabled" data-action="download-binary">
Download Binary
</a>
</li>
<li>
<a class="waves-effect waves-green btn-flat disabled action-upload" data-action="upload">Retry Upload</a>
</li>
</ul>
</div>
<!-- Update All Modal -->
<div id="js-update-all-modal" class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
<h4>Update All <code id="js-node-filename" class="inlinecode"></code></h4>
<pre id="js-log-area" class="log"></pre>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat" data-action="stop-logs">Stop</a>
</div>
</div>
<!-- Validate Modal -->
<div id="js-validate-modal" class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
<h4>Validate: <code id="js-node-filename" class="inlinecode"></code></h4>
<pre id="js-log-area" class="log"></pre>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat" data-action="edit">Edit</a>
<a class="modal-close waves-effect waves-green btn-flat" data-action="upload">Upload</a>
<a class="modal-close waves-effect waves-green btn-flat" data-action="stop-logs">Stop</a>
</div>
</div>
<!-- Logs Modal -->
<div id="js-logs-modal" class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
<!-- TODO: Change `node-filename` to `node-name` -->
<h4>Showing Logs For: <code id="js-node-filename" class="inlinecode"></code></h4>
<pre id="js-log-area" class="log"></pre>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat" data-action="stop-logs">Close</a>
</div>
</div>
<!-- Compile Modal -->
<div id="js-compile-modal" class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
<h4>Compiling: <code id="js-node-filename" class="inlinecode"></code></h4>
<pre id="js-log-area" class="log"></pre>
</div>
<div class="modal-footer">
<a href="https://esphome.io/guides/faq.html#i-can-t-get-flashing-over-usb-to-work" target="_blank"
rel="noreferrer">
<i class="material-icons">help_outline</i>
</a>
<a class="modal-close waves-effect waves-green btn-flat disabled" data-action="download-binary">
Download Binary
</a>
<a class="modal-close waves-effect waves-green btn-flat" data-action="stop-logs">Stop</a>
</div>
</div>
<!-- Clean MQTT Modal -->
<div id="js-clean-mqtt-modal" class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
<div>
<!-- TODO: Change `node-filename` to `node-name` -->
<h4>Clean MQTT Discovery For: <code id="js-node-filename" class="inlinecode"></code></h4>
<pre id="js-log-area" class="log"></pre>
</div>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat" data-action="stop-logs">Close</a>
</div>
</div>
<!-- Clean Build Files Modal -->
<div id="js-clean-modal" class="modal modal-fixed-footer no-autoinit">
<div class="modal-content">
<div>
<!-- TODO: Change `node-filename` to `node-name` -->
<h4>Clean Build Files For: <code id="js-node-filename" class="inlinecode"></code></h4>
<pre id="js-log-area" class="log"></pre>
</div>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat" data-action="stop-logs">Stop</a>
</div>
</div>
<!-- Add New Node / Launch Wizard Button -->
<div class="fixed-action-btn">
<a class="btn-floating btn-large waves-effect waves-light green js-wizard" id="js-wizard" data-action="wizard">
<i class="large material-icons">add</i>
</a>
</div>
<!-- Wizard Modal -->
<div id="js-wizard-modal" class="modal modal-fixed-footer">
<div class="modal-content">
<h5>Create New Node</h5>
<form action="./wizard.html" method="POST">
<ul class="stepper">
{% if len(entries) == 0 %}
<!-- Step 0 (First Run - No Nodes) - Welcome -->
<li class="step active">
<div class="step-title waves-effect">Welcome</div>
<div class="step-content">
<p>
Welcome to the ESPHome node setup wizard! As there are no nodes setup you will be guided through
setting up your first ESP8266 or ESP32-powered device using ESPHome.
</p>
<p>
<a href="https://www.espressif.com/en/products/hardware/esp8266ex/overview"
target="_blank">ESP8266s</a> and
their successors (the <a href="https://www.espressif.com/en/products/hardware/esp32/overview"
target="_blank">ESP32s</a>)
are great low-cost microcontrollers that can communicate with the outside world using WiFi.
They're found in many devices such as the popular Sonoff/iTead, but also exist as development boards
such as the <a href="https://esphome.io/devices/nodemcu_esp8266.html" rel="noreferrer"
target="_blank">NodeMCU</a>.
</p>
<p>
ESPHome, the tool you're using here, creates custom firmwares for these devices using YAML
configuration files (similar to the ones you might be used to with <a href="https://home-assistant.io"
target="_blank">Home Assistant</a>).
</p>
<p>
This wizard will create a basic YAML configuration file for your "node" (the microcontroller) and
later, you will be able to customize this file with some of ESPHome's many avaliable integrations.
</p>
<p>
When you are ready click "begin" to move onto the next step!
</p>
<div class="step-actions">
<button class="waves-effect waves-dark green btn next-step">Begin</button>
</div>
</div>
</li>
{% end %}
<!-- Step 1 - Node Name -->
{% if len(entries) == 0 %}
<li class="step">
{% end %}
{% if len(entries) >= 1 %}
<li class="step active">
{% end %}
<div class="step-title waves-effect">Node Name</div>
<div class="step-content">
{% if len(entries) == 0 %}
<p>
Firstly, please decide on a name for the node. Choose this name wisely, it should be unique among all
of your ESPHome nodes.
</p>
{% end %}
{% if len(entries) >= 1 %}
<p>
Select a unique name for the node.
</p>
{% end %}
<p>
Names must be all <strong>lowercase</strong> and <strong>must not contain any spaces</strong>!
Characters that are allowed are: <code class="inlinecode">a-z</code>,
<code class="inlinecode">0-9</code> and <code class="inlinecode">-</code>.
</p>
<div class="input-field col s12">
<input id="node_name" class="validate" type="text" name="name" data-rule-nospaces="true"
data-rule-lowercase="true" data-rule-nounderscores="true" required>
<label for="node_name">Node Name</label>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark green btn next-step">Next</button>
</div>
</div>
</li>
<!-- Step 2 - Device Type -->
<li class="step">
<div class="step-title waves-effect">Device Type</div>
<div class="step-content">
{% if len(entries) == 0 %}
<p>
Now that you have named your node, please select what type of microcontroller you are using so that
the firmware for your node can be compiled correctly.
</p>
{% end %}
{% if len(entries) >= 1 %}
<p>
Select the type of microcontroller that you are using for the node.
</p>
{% end %}
<p>
<em>If unsure you can also select a similar board or choose the "Generic" option.</em>
</p>
<div class="input-field col s12">
<select class="browser-default" name="board" required>
<optgroup label="ESP8266">
<option value="esp01_1m">Generic ESP8266 (for example Sonoff)</option>
<option value="nodemcuv2">NodeMCU</option>
<option value="d1_mini">Wemos D1 and Wemos D1 mini</option>
<option value="d1_mini_lite">Wemos D1 mini Lite</option>
<option value="d1_mini_pro">Wemos D1 mini Pro</option>
<option value="huzzah">Adafruit HUZZAH ESP8266</option>
<option value="oak">DigiStump Oak</option>
<option value="thing">Sparkfun ESP8266 Thing</option>
<option value="thingdev">Sparkfun ESP8266 Thing - Dev Board</option>
</optgroup>
<optgroup label="ESP32">
<option value="esp-wrover-kit">Generic ESP32 (WROVER Module)</option>
<option value="nodemcu-32s">NodeMCU-32S</option>
<option value="lolin_d32">Wemos Lolin D32</option>
<option value="lolin_d32_pro">Wemos Lolin D32 Pro</option>
<option value="featheresp32">Adafruit ESP32 Feather</option>
<option value="m5stack-core-esp32">M5Stack Core ESP32</option>
</optgroup>
<optgroup label="Other ESP8266s">
<option value="gen4iod">4D Systems gen4 IoD Range</option>
<option value="wifi_slot">Amperka WiFi Slot</option>
<option value="espduino">Doit ESPDuino</option>
<option value="espectro">DycodeX ESPectro Core</option>
<option value="espino">ESPino</option>
<option value="esp_wroom_02">Espressif ESP-WROOM-02 module</option>
<option value="esp12e">Espressif ESP-12E module</option>
<option value="esp01">Espressif ESP-01 512k module</option>
<option value="esp07">Espressif ESP-07 module</option>
<option value="esp8285">Generic ESP8285 module</option>
<option value="espresso_lite_v1">ESPert ESPresso Lite 1.0</option>
<option value="espresso_lite_v2">ESPert ESPresso Lite 2.0</option>
<option value="phoenix_v1">ESPert Phoenix 1.0</option>
<option value="wifinfo">WiFInfo</option>
<option value="heltec_wifi_kit_8">Heltec WiFi kit 8</option>
<option value="nodemcu">NodeMCU 0.9</option>
<option value="modwifi">Olimex MOD-WIFI</option>
<option value="wio_link">SeedStudio Wio Link</option>
<option value="wio_node">SeedStudio Wio Node</option>
<option value="sparkfunBlynk">Sparkfun Blynk Board</option>
<option value="esp210">SweetPea ESP-210</option>
<option value="espinotee">ThaiEasyElec ESPino</option>
<option value="d1">Wemos D1 Revision 1</option>
<option value="wifiduino">WiFiDuino</option>
<option value="xinabox_cw01">XinaBox CW01</option>
</optgroup>
<optgroup label="Other ESP32s">
<option value="lolin32">Wemos Lolin 32</option>
<option value="m5stack-fire">M5Stack FIRE</option>
<option value="wemosbat">Wemos WiFi &amp; Bluetooth Battery</option>
<option value="node32s">Node32s</option>
<option value="alksesp32">ALKS ESP32</option>
<option value="bpi-bit">BPI-Bit</option>
<option value="d-duino-32">D-duino-32</option>
<option value="esp32-devkitlipo">OLIMEX ESP32-DevKit-LiPo</option>
<option value="esp32-evb">OLIMEX ESP32-EVB</option>
<option value="esp32-gateway">OLIMEX ESP32-GATEWAY</option>
<option value="esp32-poe-iso">OLIMEX ESP32-PoE-ISO</option>
<option value="esp32-poe">OLIMEX ESP32-PoE</option>
<option value="esp32-pro">OLIMEX ESP32-PRO</option>
<option value="esp320">Electronic SweetPeas ESP320</option>
<option value="esp32cam">AI Thinker ESP32-CAM</option>
<option value="esp32dev">Espressif ESP32 Dev Module</option>
<option value="esp32doit-devkit-v1">DOIT ESP32 DEVKIT V1</option>
<option value="esp32thing">SparkFun ESP32 Thing</option>
<option value="esp32vn-iot-uno">ESP32vn IoT Uno</option>
<option value="espea32">April Brother ESPea32</option>
<option value="espectro32">ESPectro32</option>
<option value="espino32">ESPino32</option>
<option value="firebeetle32">FireBeetle-ESP32</option>
<option value="fm-devkit">ESP32 FM DevKit</option>
<option value="frogboard">Frog Board ESP32</option>
<option value="heltec_wifi_kit_32">Heltec WiFi Kit 32</option>
<option value="heltec_wifi_lora_32">Heltec WiFi LoRa 32</option>
<option value="heltec_wifi_lora_32_V2">Heltec WiFi LoRa 32 (V2)</option>
<option value="heltec_wireless_stick">Heltec Wireless Stick</option>
<option value="hornbill32dev">Hornbill ESP32 Dev</option>
<option value="hornbill32minima">Hornbill ESP32 Minima</option>
<option value="intorobot">IntoRobot Fig</option>
<option value="iotaap_magnolia">IoTaaP Magnolia</option>
<option value="iotbusio">oddWires IoT-Bus Io</option>
<option value="iotbusproteus">oddWires IoT-Bus Proteus</option>
<option value="lopy">Pycom LoPy</option>
<option value="lopy4">Pycom LoPy4</option>
<option value="m5stack-grey">M5Stack GREY ESP32</option>
<option value="m5stick-c">M5Stick-C</option>
<option value="magicbit">MagicBit</option>
<option value="mhetesp32devkit">MH ET LIVE ESP32DevKIT</option>
<option value="mhetesp32minikit">MH ET LIVE ESP32MiniKit</option>
<option value="microduino-core-esp32">Microduino Core ESP32</option>
<option value="nano32">MakerAsia Nano32</option>
<option value="nina_w10">u-blox NINA-W10 series</option>
<option value="odroid_esp32">ODROID-GO</option>
<option value="onehorse32dev">Onehorse ESP32 Dev Module</option>
<option value="oroca_edubot">OROCA EduBot</option>
<option value="pico32">ESP32 Pico Kit</option>
<option value="pocket_32">Dongsen Tech Pocket 32</option>
<option value="pycom_gpy">Pycom GPy</option>
<option value="quantum">Noduino Quantum</option>
<option value="sparkfun_lora_gateway_1-channel">SparkFun LoRa Gateway 1-Channel</option>
<option value="tinypico">TinyPICO</option>
<option value="ttgo-lora32-v1">TTGO LoRa32-OLED V1</option>
<option value="ttgo-t-beam">TTGO T-Beam</option>
<option value="ttgo-t-watch">TTGO T-Watch</option>
<option value="ttgo-t1">TTGO T1</option>
<option value="turta_iot_node">Turta IoT Node</option>
<option value="vintlabs-devkit-v1">VintLabs ESP32 Devkit</option>
<option value="wemos_d1_mini32">WeMos D1 MINI ESP32</option>
<option value="wesp32">Silicognition wESP32</option>
<option value="widora-air">Widora AIR</option>
<option value="xinabox_cw02">XinaBox CW02</option>
</optgroup>
</select>
</div>
<div class="step-actions">
<button class="waves-effect waves-dark green btn next-step">Next</button>
</div>
</div>
</li>
<!-- Step 3 - WiFi & Updates -->
<li class="step">
<div class="step-title waves-effect">WiFi & Updates</div>
<div class="step-content">
{% if len(entries) == 0 %}
<p>
Finally, please enter the details for the WiFi network you want the node to connect to.
Please enter an SSID (name of the WiFi network) and the password needed to connect (if the network has
no password then this can be left blank):
</p>
{% end %}
{% if len(entries) >= 1 %}
<p>
Enter the details for the WiFi network the node can connect to:
</p>
{% end %}
<div class="input-field col s12">
<input id="wifi_ssid" class="validate" type="text" name="ssid" required>
<label for="wifi_ssid">WiFi SSID</label>
</div>
<div class="input-field col s12">
<input id="wifi_password" name="psk" type="password">
<label for="wifi_password">WiFi Password</label>
</div>
{% if len(entries) == 0 %}
<p>
This wizard will automatically set up an Over-The-Air (OTA) update server on the node so that you only
need
to flash the firmware via USB once.
You can optionally password protect this OTA update server by setting up a password here:
</p>
{% end %}
{% if len(entries) >= 1 %}
<p>
Setup an optional password for the Over-The-Air (OTA) update server:
</p>
{% end %}
<div class="input-field col s12">
<input id="password" class="validate" name="password" type="password">
<label for="password">OTA Access Password</label>
</div>
<div class="step-actions">
<!-- Here goes your actions buttons -->
<button class="waves-effect waves-dark green btn next-step">Next</button>
</div>
</div>
</li>
<!-- Step 3 - Finish -->
<li class="step">
<div class="step-title waves-effect">Finish</div>
<div class="step-content">
{% if len(entries) == 0 %}
<p>
Hooray! 🎉🎉🎉 You have successfully created your first ESPHome configuration file.
When you click Submit, the wizard will save the configuration file as
<code class="inlinecode">{{ escape(config_dir) }}/&lt;NAME_OF_NODE&gt;.yaml</code>.
</p>
<h5>Next steps</h5>
<ul class="browser-default">
<li>
Flash the firmware. This can be done using the “UPLOAD” option in the dashboard. See
<a href="https://esphome.io/index.html#devices" rel="noreferrer" target="_blank">this</a>
for guides on how to flash different types of devices. For newly plugged in serial
devices to be detected, restart the add-on.
</li>
<li>
With the current configuration, your node will only connect to WiFi. To make it
actually <i>do</i> stuff, follow
<a href="https://esphome.io/guides/getting_started_hassio.html#adding-some-basic-features"
rel="noreferrer">
the rest of the getting started guide
</a>.
</li>
<li>
See the <a href="https://esphome.io/index.html" rel="noreferrer" target="_blank">ESPHome
Documentation</a>
for a list of supported sensors/devices.
</li>
<li>
Join the <a href="https://discord.gg/KhAMKrd" target="_blank">Discord server</a> and
say hi! Discord's the best place to ask if you have issues/ideas.
</li>
<li>
Star <a href="https://github.com/esphome/esphome" target="_blank">ESPHome</a> on GitHub
if you find this software awesome and report issues using the bug trackers there.
</li>
</ul>
{% end %}
{% if len(entries) >= 1 %}
<p>Click submit to finish creating the node!</p>
{% end %}
<div class="step-actions">
<!-- Here goes your actions buttons -->
<button class="waves-effect waves-dark green btn" type="submit">Submit</button>
</div>
</div>
</li>
</ul>
</form>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat" onclick="wizardStepperInstace.resetStepper();">Exit
Wizard</a>
</div>
</div>
</main>
<footer class="page-footer grey darken-4">
<div class="container">
<div class="left">
Copyright © 2019-2021 ESPHome | Made with <a href="https://materializecss.com/" target="_blank">Materialize</a>
</div>
<div class="right">
<a href="{{ docs_link }}" target="_blank" rel="noreferrer">v{{ version }}
Documentation</a>
</div>
</div>
</footer>
<script src="{{ get_static_file_url('js/vendor/ace/ace.js') }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ get_static_file_url('js/esphome.js') }}" type="text/javascript"></script>
{% if begin and len(entries) == 1 %}
<script>
window.history.replaceState({}, document.title, "/");
document.addEventListener('DOMContentLoaded', () => {
M.toast({
html: '🎉 Congratulations on adding your first Node!',
displayLength: 10000
})
});
</script>
{% end %}
</body>
</html>

View file

@ -1,93 +0,0 @@
<!DOCTYPE html>
<html lang="en" id="login-page">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - ESPHome</title>
<link rel="shortcut icon" href="{{ get_static_file_url('images/favicon.ico') }}">
<link rel="stylesheet" href="{{ get_static_file_url('fonts/material-icons/material-icons.css') }}">
<link rel="stylesheet" href="{{ get_static_file_url('css/vendor/materialize/materialize.min.css') }}">
<link rel="stylesheet" href="{{ get_static_file_url('css/esphome.css') }}">
<script src="{{ get_static_file_url('js/vendor/jquery/jquery.min.js') }}"></script>
<script src="{{ get_static_file_url('js/vendor/materialize/materialize.min.js') }}"></script>
</head>
<body>
<div class="valign-wrapper">
<div class="valign">
<div class="container">
<div class="row">
<div class="col s12 m8 offset-m2 l8 offset-l2 xl6 offset-xl3">
<div class="card" id="login-card">
<div class="card-content">
<img src="https://esphome.io/_static/logo-text.svg" alt="ESPHome Logo" class="logo">
<span class="version-number center">v{{ version }}</span>
<span class="card-title black-text center">Dashboard Login</span>
<p class="center">
{% if hassio %}
Login by entering your Home Assistant login credentials.
{% else %}
Login by entering your ESPHome login credentials.
{% end %}
</p>
{% if error is not None %}
<div class="alert alert-error">
<span class="title">Error!</span>
{{ escape(error) }}
</div>
<script>
$("#login-card").addClass("card-error");
</script>
{% end %}
<form action="./login" method="post" id="login-form">
{% if has_username or hassio %}
<div class="row no-bottom-margin">
<div class="input-field col s12">
<i class="material-icons prefix">person</i>
<input name="username" id="username" type="text">
<label for="username">Username</label>
</div>
</div>
{% end %}
<div class="row no-bottom-margin">
<div class="input-field col s12">
<i class="material-icons prefix">lock</i>
<input name="password" id="password" type="password">
<label for="password">Password</label>
</div>
</div>
</form>
</div>
<div class="card-action center">
<input type="submit" class="btn blue-grey darken-2" name="action" form="login-form" value="Login">
</div>
</div>
</div>
</div>
</div>
<footer class="center-align">
<p>Copyright © 2019-2021 ESPHome.</p>
<p>
<a href="{{ docs_link }}" target="_blank" rel="noreferrer">ESPHome
{{ version }} Documentation</a>
</p>
<p>
Made with <a href="https://materializecss.com/" target="_blank">Materialize.</a>
</p>
</footer>
</div>
</div>
</body>
</html>

View file

@ -49,17 +49,6 @@ BASE_CONFIG = """esphome:
platform: {platform} platform: {platform}
board: {board} board: {board}
wifi:
ssid: "{ssid}"
password: "{psk}"
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "{fallback_name}"
password: "{fallback_psk}"
captive_portal:
# Enable logging # Enable logging
logger: logger:
@ -83,12 +72,43 @@ def wizard_file(**kwargs):
config = BASE_CONFIG.format(**kwargs) config = BASE_CONFIG.format(**kwargs)
if kwargs["password"]: # Configure API
config += ' password: "{0}"\n\nota:\n password: "{0}"\n'.format( if "password" in kwargs:
kwargs["password"] config += ' password: "{0}"\n'.format(kwargs["password"])
# Configure OTA
config += "\nota:\n"
if "ota_password" in kwargs:
config += ' password: "{0}"'.format(kwargs["ota_password"])
elif "password" in kwargs:
config += ' password: "{0}"'.format(kwargs["password"])
# Configuring wifi
config += "\n\nwifi:\n"
if "ssid" in kwargs:
config += """ ssid: "{ssid}"
password: "{psk}"
""".format(
**kwargs
) )
else: else:
config += "\nota:\n" config += """ # ssid: "My SSID"
# password: "mypassword"
networks:
"""
config += """
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "{fallback_name}"
password: "{fallback_psk}"
captive_portal:
""".format(
**kwargs
)
return config return config
@ -97,9 +117,9 @@ def wizard_write(path, **kwargs):
name = kwargs["name"] name = kwargs["name"]
board = kwargs["board"] board = kwargs["board"]
kwargs["ssid"] = sanitize_double_quotes(kwargs["ssid"]) for key in ("ssid", "psk", "password", "ota_password"):
kwargs["psk"] = sanitize_double_quotes(kwargs["psk"]) if key in kwargs:
kwargs["password"] = sanitize_double_quotes(kwargs["password"]) kwargs[key] = sanitize_double_quotes(kwargs[key])
if "platform" not in kwargs: if "platform" not in kwargs:
kwargs["platform"] = "ESP8266" if board in ESP8266_BOARD_PINS else "ESP32" kwargs["platform"] = "ESP8266" if board in ESP8266_BOARD_PINS else "ESP32"

View file

@ -11,3 +11,4 @@ ifaddr==0.1.7
platformio==5.1.1 platformio==5.1.1
esptool==2.8 esptool==2.8
click==7.1.2 click==7.1.2
esphome-dashboard==20210611.0