Dashboard Update all button (#615)

* Add update all button

* Use bold
This commit is contained in:
Otto Winter 2019-06-07 14:26:28 +02:00 committed by GitHub
parent 4fe0c95ccb
commit 7a895adec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 128 additions and 27 deletions

View File

@ -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.core import CORE, EsphomeError, coroutine, coroutine_with_priority
from esphome.helpers import color, indent from esphome.helpers import color, indent
from esphome.py_compat import IS_PY2, safe_input 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__) _LOGGER = logging.getLogger(__name__)
@ -187,8 +187,7 @@ def upload_program(config, args, host):
ota_conf = config[CONF_OTA] ota_conf = config[CONF_OTA]
remote_port = ota_conf[CONF_PORT] remote_port = ota_conf[CONF_PORT]
password = ota_conf[CONF_PASSWORD] password = ota_conf[CONF_PASSWORD]
res = espota2.run_ota(host, remote_port, password, CORE.firmware_bin) return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
return res
def show_logs(config, args, port): def show_logs(config, args, port):
@ -350,11 +349,52 @@ def command_dashboard(args):
return dashboard.start_web_server(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 = { PRE_CONFIG_ACTIONS = {
'wizard': command_wizard, 'wizard': command_wizard,
'version': command_version, 'version': command_version,
'dashboard': command_dashboard, 'dashboard': command_dashboard,
'vscode': command_vscode, 'vscode': command_vscode,
'update-all': command_update_all,
} }
POST_CONFIG_ACTIONS = { POST_CONFIG_ACTIONS = {
@ -446,6 +486,8 @@ def parse_args(argv):
vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS) vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
vscode.add_argument('--ace', action='store_true') vscode.add_argument('--ace', action='store_true')
subparsers.add_parser('update-all', help=argparse.SUPPRESS)
return parser.parse_args(argv[1:]) return parser.parse_args(argv[1:])

View File

@ -26,7 +26,7 @@ import tornado.process
import tornado.web import tornado.web
import tornado.websocket import tornado.websocket
from esphome import const from esphome import const, util
from esphome.__main__ import get_serial_ports from esphome.__main__ import get_serial_ports
from esphome.helpers import mkdir_p, get_bool_env, run_system_command from esphome.helpers import mkdir_p, get_bool_env, run_system_command
from esphome.py_compat import IS_PY2, decode_text from esphome.py_compat import IS_PY2, decode_text
@ -93,17 +93,7 @@ class DashboardSettings(object):
return os.path.join(self.config_dir, *args) return os.path.join(self.config_dir, *args)
def list_yaml_files(self): def list_yaml_files(self):
files = [] return util.list_yaml_files(self.config_dir)
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
settings = DashboardSettings() settings = DashboardSettings()
@ -122,6 +112,7 @@ def template_args():
'get_static_file_url': get_static_file_url, 'get_static_file_url': get_static_file_url,
'relative_url': settings.relative_url, 'relative_url': settings.relative_url,
'streamer_mode': get_bool_env('ESPHOME_STREAMER_MODE'), '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"] 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): class SerialPortRequestHandler(BaseHandler):
@authenticated @authenticated
def get(self): def get(self):
@ -690,6 +686,7 @@ def make_app(debug=False):
(rel + "clean", EsphomeCleanHandler), (rel + "clean", EsphomeCleanHandler),
(rel + "vscode", EsphomeVscodeHandler), (rel + "vscode", EsphomeVscodeHandler),
(rel + "ace", EsphomeAceEditorHandler), (rel + "ace", EsphomeAceEditorHandler),
(rel + "update-all", EsphomeUpdateAllHandler),
(rel + "edit", EditRequestHandler), (rel + "edit", EditRequestHandler),
(rel + "download.bin", DownloadBinaryRequestHandler), (rel + "download.bin", DownloadBinaryRequestHandler),
(rel + "serial-ports", SerialPortRequestHandler), (rel + "serial-ports", SerialPortRequestHandler),

View File

@ -131,10 +131,14 @@ ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .s
.select-port-container { .select-port-container {
margin-top: 8px; margin-top: 8px;
margin-right: 24px; margin-right: 10px;
width: 350px; width: 350px;
} }
#dropdown-nav-trigger {
margin-right: 24px;
}
.select-port-container .select-dropdown { .select-port-container .select-dropdown {
color: #fff; color: #fff;
} }

View File

@ -333,6 +333,10 @@ class LogModalElem {
this.activeSocket.close(); this.activeSocket.close();
} }
open(event) {
this._onPress(event);
}
_onPress(event) { _onPress(event) {
this.activeConfig = event.target.getAttribute('data-node'); this.activeConfig = event.target.getAttribute('data-node');
this._setupModalInstance(); this._setupModalInstance();
@ -745,3 +749,32 @@ jQuery.validator.addMethod("nospaces", (value, element) => {
jQuery.validator.addMethod("lowercase", (value, element) => { jQuery.validator.addMethod("lowercase", (value, element) => {
return value === value.toLowerCase(); return value === value.toLowerCase();
}, "Name must be lowercase."); }, "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);
});

View File

@ -31,10 +31,16 @@
<nav> <nav>
<div class="nav-wrapper indigo"> <div class="nav-wrapper indigo">
<a href="#" class="brand-logo left">ESPHome Dashboard</a> <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"> <div class="select-port-container right" id="select-port-target">
<select></select> <select></select>
</div> </div>
</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> </nav>
{% if begin %} {% if begin %}
@ -445,6 +451,16 @@
</div> </div>
</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"> <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> <i class="material-icons">add</i>
</a> </a>

View File

@ -299,3 +299,4 @@ def run_ota(remote_host, remote_port, password, filename):
return run_ota_impl_(remote_host, remote_port, password, filename) return run_ota_impl_(remote_host, remote_port, password, filename)
except OTAError as err: except OTAError as err:
_LOGGER.error(err) _LOGGER.error(err)
return 1

View File

@ -3,6 +3,7 @@ from __future__ import print_function
import collections import collections
import io import io
import logging import logging
import os
import re import re
import subprocess import subprocess
import sys import sys
@ -207,3 +208,16 @@ class OrderedDict(collections.OrderedDict):
root[1] = first[0] = link root[1] = first[0] = link
else: else:
super(OrderedDict, self).move_to_end(key, last=last) # pylint: disable=no-member 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

View File

@ -14,7 +14,7 @@ from esphome import core
from esphome.config_helpers import read_config_file from esphome.config_helpers import read_config_file
from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, DocumentRange
from esphome.py_compat import text_type, IS_PY2 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__) _LOGGER = logging.getLogger(__name__)
@ -260,12 +260,12 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
@_add_data_ref @_add_data_ref
def construct_include_dir_list(self, node): 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] return [_load_yaml_internal(f) for f in files]
@_add_data_ref @_add_data_ref
def construct_include_dir_merge_list(self, node): 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 = [] merged_list = []
for fname in files: for fname in files:
loaded_yaml = _load_yaml_internal(fname) loaded_yaml = _load_yaml_internal(fname)
@ -275,7 +275,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
@_add_data_ref @_add_data_ref
def construct_include_dir_named(self, node): 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() mapping = OrderedDict()
for fname in files: for fname in files:
filename = os.path.splitext(os.path.basename(fname))[0] 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 @_add_data_ref
def construct_include_dir_merge_named(self, node): 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() mapping = OrderedDict()
for fname in files: for fname in files:
loaded_yaml = _load_yaml_internal(fname) 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)) 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: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:float', ESPHomeLoader.construct_yaml_float)
ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary) ESPHomeLoader.add_constructor(u'tag:yaml.org,2002:binary', ESPHomeLoader.construct_yaml_binary)