mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 05:24:53 +01:00
parent
4fe0c95ccb
commit
7a895adec9
8 changed files with 128 additions and 27 deletions
|
@ -15,7 +15,7 @@ from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
|
|||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||
from esphome.helpers import color, indent
|
||||
from esphome.py_compat import IS_PY2, safe_input
|
||||
from esphome.util import run_external_command, run_external_process, safe_print
|
||||
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -187,8 +187,7 @@ def upload_program(config, args, host):
|
|||
ota_conf = config[CONF_OTA]
|
||||
remote_port = ota_conf[CONF_PORT]
|
||||
password = ota_conf[CONF_PASSWORD]
|
||||
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
return res
|
||||
return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
|
||||
|
||||
|
||||
def show_logs(config, args, port):
|
||||
|
@ -350,11 +349,52 @@ def command_dashboard(args):
|
|||
return dashboard.start_web_server(args)
|
||||
|
||||
|
||||
def command_update_all(args):
|
||||
import click
|
||||
|
||||
success = {}
|
||||
files = list_yaml_files(args.configuration)
|
||||
twidth = 60
|
||||
|
||||
def print_bar(middle_text):
|
||||
middle_text = " {} ".format(middle_text)
|
||||
width = len(click.unstyle(middle_text))
|
||||
half_line = "=" * ((twidth - width) / 2)
|
||||
click.echo("%s%s%s" % (half_line, middle_text, half_line))
|
||||
|
||||
for f in files:
|
||||
print("Updating {}".format(color('cyan', f)))
|
||||
print('-' * twidth)
|
||||
print()
|
||||
rc = run_external_process('esphome', f, 'run', '--no-logs')
|
||||
if rc == 0:
|
||||
print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
|
||||
success[f] = True
|
||||
else:
|
||||
print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
|
||||
success[f] = False
|
||||
|
||||
print()
|
||||
print()
|
||||
print()
|
||||
|
||||
print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
|
||||
failed = 0
|
||||
for f in files:
|
||||
if success[f]:
|
||||
print(" - {}: {}".format(f, color('green', 'SUCCESS')))
|
||||
else:
|
||||
print(" - {}: {}".format(f, color('bold_red', 'FAILED')))
|
||||
failed += 1
|
||||
return failed
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
'wizard': command_wizard,
|
||||
'version': command_version,
|
||||
'dashboard': command_dashboard,
|
||||
'vscode': command_vscode,
|
||||
'update-all': command_update_all,
|
||||
}
|
||||
|
||||
POST_CONFIG_ACTIONS = {
|
||||
|
@ -446,6 +486,8 @@ def parse_args(argv):
|
|||
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
|
||||
vscode.add_argument('--ace', action='store_true')
|
||||
|
||||
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
|
||||
|
||||
return parser.parse_args(argv[1:])
|
||||
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import tornado.process
|
|||
import tornado.web
|
||||
import tornado.websocket
|
||||
|
||||
from esphome import const
|
||||
from esphome import const, util
|
||||
from esphome.__main__ import get_serial_ports
|
||||
from esphome.helpers import mkdir_p, get_bool_env, run_system_command
|
||||
from esphome.py_compat import IS_PY2, decode_text
|
||||
|
@ -93,17 +93,7 @@ class DashboardSettings(object):
|
|||
return os.path.join(self.config_dir, *args)
|
||||
|
||||
def list_yaml_files(self):
|
||||
files = []
|
||||
for file in os.listdir(self.config_dir):
|
||||
if not file.endswith('.yaml'):
|
||||
continue
|
||||
if file.startswith('.'):
|
||||
continue
|
||||
if file == 'secrets.yaml':
|
||||
continue
|
||||
files.append(file)
|
||||
files.sort()
|
||||
return files
|
||||
return util.list_yaml_files(self.config_dir)
|
||||
|
||||
|
||||
settings = DashboardSettings()
|
||||
|
@ -122,6 +112,7 @@ def template_args():
|
|||
'get_static_file_url': get_static_file_url,
|
||||
'relative_url': settings.relative_url,
|
||||
'streamer_mode': get_bool_env('ESPHOME_STREAMER_MODE'),
|
||||
'config_dir': settings.config_dir,
|
||||
}
|
||||
|
||||
|
||||
|
@ -315,6 +306,11 @@ class EsphomeAceEditorHandler(EsphomeCommandWebSocket):
|
|||
return ["esphome", "--dashboard", "-q", settings.config_dir, "vscode", "--ace"]
|
||||
|
||||
|
||||
class EsphomeUpdateAllHandler(EsphomeCommandWebSocket):
|
||||
def build_command(self, json_message):
|
||||
return ["esphome", "--dashboard", settings.config_dir, "update-all"]
|
||||
|
||||
|
||||
class SerialPortRequestHandler(BaseHandler):
|
||||
@authenticated
|
||||
def get(self):
|
||||
|
@ -690,6 +686,7 @@ def make_app(debug=False):
|
|||
(rel + "clean", EsphomeCleanHandler),
|
||||
(rel + "vscode", EsphomeVscodeHandler),
|
||||
(rel + "ace", EsphomeAceEditorHandler),
|
||||
(rel + "update-all", EsphomeUpdateAllHandler),
|
||||
(rel + "edit", EditRequestHandler),
|
||||
(rel + "download.bin", DownloadBinaryRequestHandler),
|
||||
(rel + "serial-ports", SerialPortRequestHandler),
|
||||
|
|
|
@ -131,10 +131,14 @@ ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .s
|
|||
|
||||
.select-port-container {
|
||||
margin-top: 8px;
|
||||
margin-right: 24px;
|
||||
margin-right: 10px;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
#dropdown-nav-trigger {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.select-port-container .select-dropdown {
|
||||
color: #fff;
|
||||
}
|
||||
|
|
|
@ -333,6 +333,10 @@ class LogModalElem {
|
|||
this.activeSocket.close();
|
||||
}
|
||||
|
||||
open(event) {
|
||||
this._onPress(event);
|
||||
}
|
||||
|
||||
_onPress(event) {
|
||||
this.activeConfig = event.target.getAttribute('data-node');
|
||||
this._setupModalInstance();
|
||||
|
@ -745,3 +749,32 @@ jQuery.validator.addMethod("nospaces", (value, element) => {
|
|||
jQuery.validator.addMethod("lowercase", (value, element) => {
|
||||
return value === value.toLowerCase();
|
||||
}, "Name must be lowercase.");
|
||||
|
||||
|
||||
|
||||
const updateAllModal = new LogModalElem({
|
||||
name: 'update-all',
|
||||
onPrepare: (modalElem, config) => {
|
||||
modalElem.querySelector('.stop-logs').innerHTML = "Stop";
|
||||
downloadButton.classList.add('disabled');
|
||||
},
|
||||
onProcessExit: (modalElem, code) => {
|
||||
if (code === 0) {
|
||||
M.toast({html: "Program exited successfully."});
|
||||
downloadButton.classList.remove('disabled');
|
||||
} else {
|
||||
M.toast({html: `Program failed with code ${data.code}`});
|
||||
}
|
||||
modalElem.querySelector(".stop-logs").innerHTML = "Close";
|
||||
},
|
||||
onSocketClose: (modalElem) => {
|
||||
M.toast({html: 'Terminated process.'});
|
||||
},
|
||||
dismissible: false,
|
||||
});
|
||||
updateAllModal.setup();
|
||||
|
||||
const updateAllButton = document.getElementById('update-all-button');
|
||||
updateAllButton.addEventListener('click', (e) => {
|
||||
updateAllModal.open(e);
|
||||
});
|
||||
|
|
|
@ -31,10 +31,16 @@
|
|||
<nav>
|
||||
<div class="nav-wrapper indigo">
|
||||
<a href="#" class="brand-logo left">ESPHome Dashboard</a>
|
||||
<i class="material-icons dropdown-trigger right" id="dropdown-nav-trigger" data-target="dropdown-nav-actions">more_vert</i>
|
||||
<div class="select-port-container right" id="select-port-target">
|
||||
<select></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="dropdown-nav-actions" class="select-action dropdown-content card-dropdown-action">
|
||||
<li><a id="update-all-button" class="modal-close waves-effect waves-green btn-flat"
|
||||
data-node="{{ escape(config_dir) }}">Update All</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{% if begin %}
|
||||
|
@ -445,6 +451,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modal-update-all" class="modal modal-fixed-footer">
|
||||
<div class="modal-content">
|
||||
<h4>Update All</h4>
|
||||
<pre class="log"></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="btn-floating btn-large ribbon-fab waves-effect waves-light pink accent-2" id="setup-wizard-start">
|
||||
<i class="material-icons">add</i>
|
||||
</a>
|
||||
|
|
|
@ -299,3 +299,4 @@ def run_ota(remote_host, remote_port, password, filename):
|
|||
return run_ota_impl_(remote_host, remote_port, password, filename)
|
||||
except OTAError as err:
|
||||
_LOGGER.error(err)
|
||||
return 1
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import print_function
|
|||
import collections
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
@ -207,3 +208,16 @@ class OrderedDict(collections.OrderedDict):
|
|||
root[1] = first[0] = link
|
||||
else:
|
||||
super(OrderedDict, self).move_to_end(key, last=last) # pylint: disable=no-member
|
||||
|
||||
|
||||
def list_yaml_files(folder):
|
||||
files = filter_yaml_files([os.path.join(folder, p) for p in os.listdir(folder)])
|
||||
files.sort()
|
||||
return files
|
||||
|
||||
|
||||
def filter_yaml_files(files):
|
||||
files = [f for f in files if os.path.splitext(f)[1] == '.yaml']
|
||||
files = [f for f in files if os.path.basename(f) != 'secrets.yaml']
|
||||
files = [f for f in files if not os.path.basename(f).startswith('.')]
|
||||
return files
|
||||
|
|
|
@ -14,7 +14,7 @@ from esphome import core
|
|||
from esphome.config_helpers import read_config_file
|
||||
from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange
|
||||
from esphome.py_compat import text_type, IS_PY2
|
||||
from esphome.util import OrderedDict
|
||||
from esphome.util import OrderedDict, filter_yaml_files
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -260,12 +260,12 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_list(self, node):
|
||||
files = _filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||
return [_load_yaml_internal(f) for f in files]
|
||||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_merge_list(self, node):
|
||||
files = _filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||
merged_list = []
|
||||
for fname in files:
|
||||
loaded_yaml = _load_yaml_internal(fname)
|
||||
|
@ -275,7 +275,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_named(self, node):
|
||||
files = _filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||
mapping = OrderedDict()
|
||||
for fname in files:
|
||||
filename = os.path.splitext(os.path.basename(fname))[0]
|
||||
|
@ -284,7 +284,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||
|
||||
@_add_data_ref
|
||||
def construct_include_dir_merge_named(self, node):
|
||||
files = _filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||
files = filter_yaml_files(_find_files(self._rel_path(node.value), '*.yaml'))
|
||||
mapping = OrderedDict()
|
||||
for fname in files:
|
||||
loaded_yaml = _load_yaml_internal(fname)
|
||||
|
@ -297,12 +297,6 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
|||
return Lambda(text_type(node.value))
|
||||
|
||||
|
||||
def _filter_yaml_files(files):
|
||||
files = [f for f in files if os.path.basename(f) != SECRET_YAML]
|
||||
files = [f for f in files if not os.path.basename(f).startswith('.')]
|
||||
return files
|
||||
|
||||
|
||||
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:int', ESPHomeLoader.construct_yaml_int)
|
||||
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:float', ESPHomeLoader.construct_yaml_float)
|
||||
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary)
|
||||
|
|
Loading…
Reference in a new issue