1
0
mirror of https://github.com/esphome/esphome.git synced 2025-02-13 00:18:21 +00:00

Better ANSI color escaping

This commit is contained in:
Otto Winter 2018-11-24 18:32:18 +01:00
parent 8587e7ad74
commit 41d5dcded1
No known key found for this signature in database
GPG Key ID: DB66C0BE6013F97E
8 changed files with 488 additions and 440 deletions

View File

@ -63,7 +63,7 @@ def choose_serial_port(config):
return result[opt][0] return result[opt][0]
def run_miniterm(config, port, escape=False): def run_miniterm(config, port):
import serial import serial
if CONF_LOGGER not in config: if CONF_LOGGER not in config:
_LOGGER.info("Logger is not enabled. Not starting UART logs.") _LOGGER.info("Logger is not enabled. Not starting UART logs.")
@ -84,7 +84,7 @@ def run_miniterm(config, port, escape=False):
line = raw.replace('\r', '').replace('\n', '') line = raw.replace('\r', '').replace('\n', '')
time = datetime.now().time().strftime('[%H:%M:%S]') time = datetime.now().time().strftime('[%H:%M:%S]')
message = time + line message = time + line
if escape: if core.FROM_DASHBOARD:
message = message.replace('\033', '\\033') message = message.replace('\033', '\\033')
safe_print(message) safe_print(message)
@ -152,7 +152,7 @@ def upload_program(config, args, port):
# if upload is to a serial port use platformio, otherwise assume ota # if upload is to a serial port use platformio, otherwise assume ota
serial_port = port.startswith('/') or port.startswith('COM') serial_port = port.startswith('/') or port.startswith('COM')
if port != 'OTA' and serial_port: if port != 'OTA' and serial_port:
if CORE.is_esp8266 and args.use_esptoolpy: if CORE.is_esp8266 and args.dashboard:
return upload_using_esptool(config, port) return upload_using_esptool(config, port)
return platformio_api.run_upload(config, args.verbose, port) return platformio_api.run_upload(config, args.verbose, port)
@ -187,13 +187,12 @@ def upload_program(config, args, port):
CORE.firmware_bin) CORE.firmware_bin)
def show_logs(config, args, port, escape=False): def show_logs(config, args, port):
serial_port = port.startswith('/') or port.startswith('COM') serial_port = port.startswith('/') or port.startswith('COM')
if port != 'OTA' and serial_port: if port != 'OTA' and serial_port:
run_miniterm(config, port, escape=escape) run_miniterm(config, port)
return 0 return 0
return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id, return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
escape=escape)
def clean_mqtt(config, args): def clean_mqtt(config, args):
@ -282,7 +281,7 @@ def command_upload(args, config):
def command_logs(args, config): def command_logs(args, config):
port = args.serial_port or choose_serial_port(config) port = args.serial_port or choose_serial_port(config)
return show_logs(config, args, port, escape=args.escape) return show_logs(config, args, port)
def command_run(args, config): def command_run(args, config):
@ -300,7 +299,7 @@ def command_run(args, config):
_LOGGER.info(u"Successfully uploaded program.") _LOGGER.info(u"Successfully uploaded program.")
if args.no_logs: if args.no_logs:
return 0 return 0
return show_logs(config, args, port, escape=args.escape) return show_logs(config, args, port)
def command_clean_mqtt(args, config): def command_clean_mqtt(args, config):
@ -381,21 +380,24 @@ def parse_args(argv):
subparsers = parser.add_subparsers(help='Commands', dest='command') subparsers = parser.add_subparsers(help='Commands', dest='command')
subparsers.required = True subparsers.required = True
subparsers.add_parser('config', help='Validate the configuration and spit it out.') config = subparsers.add_parser('config', help='Validate the configuration and spit it out.')
config.add_argument('--dashboard', help="Internal flag used by the dashboard",
action='store_true')
parser_compile = subparsers.add_parser('compile', parser_compile = subparsers.add_parser('compile',
help='Read the configuration and compile a program.') help='Read the configuration and compile a program.')
parser_compile.add_argument('--only-generate', parser_compile.add_argument('--only-generate',
help="Only generate source code, do not compile.", help="Only generate source code, do not compile.",
action='store_true') action='store_true')
parser_compile.add_argument('--dashboard', help="Internal flag used by the dashboard",
action='store_true')
parser_upload = subparsers.add_parser('upload', help='Validate the configuration ' parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
'and upload the latest binary.') 'and upload the latest binary.')
parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. " parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
"For example /dev/cu.SLAB_USBtoUART.") "For example /dev/cu.SLAB_USBtoUART.")
parser_upload.add_argument('--host-port', help="Specify the host port.", type=int) parser_upload.add_argument('--host-port', help="Specify the host port.", type=int)
parser_upload.add_argument('--use-esptoolpy', parser_upload.add_argument('--dashboard', help="Internal flag used by the dashboard",
help="Use esptool.py for the uploading (only for ESP8266)",
action='store_true') action='store_true')
parser_logs = subparsers.add_parser('logs', help='Validate the configuration ' parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
@ -406,7 +408,7 @@ def parse_args(argv):
parser_logs.add_argument('--client-id', help='Manually set the client id.') parser_logs.add_argument('--client-id', help='Manually set the client id.')
parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use" parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
"For example /dev/cu.SLAB_USBtoUART.") "For example /dev/cu.SLAB_USBtoUART.")
parser_logs.add_argument('--escape', help="Escape ANSI color codes for running in dashboard", parser_logs.add_argument('--dashboard', help="Internal flag used by the dashboard",
action='store_true') action='store_true')
parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, ' parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
@ -420,10 +422,7 @@ def parse_args(argv):
parser_run.add_argument('--username', help='Manually set the MQTT username for logs.') parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
parser_run.add_argument('--password', help='Manually set the MQTT password for logs.') parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
parser_run.add_argument('--client-id', help='Manually set the client id for logs.') parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
parser_run.add_argument('--escape', help="Escape ANSI color codes for running in dashboard", parser_run.add_argument('--dashboard', help="Internal flag used by the dashboard",
action='store_true')
parser_run.add_argument('--use-esptoolpy',
help="Use esptool.py for the uploading (only for ESP8266)",
action='store_true') action='store_true')
parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from " parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
@ -432,6 +431,8 @@ def parse_args(argv):
parser_clean.add_argument('--username', help='Manually set the username.') parser_clean.add_argument('--username', help='Manually set the username.')
parser_clean.add_argument('--password', help='Manually set the password.') parser_clean.add_argument('--password', help='Manually set the password.')
parser_clean.add_argument('--client-id', help='Manually set the client id.') parser_clean.add_argument('--client-id', help='Manually set the client id.')
parser_clean.add_argument('--dashboard', help="Internal flag used by the dashboard",
action='store_true')
subparsers.add_parser('wizard', help="A helpful setup wizard that will guide " subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
"you through setting up esphomeyaml.") "you through setting up esphomeyaml.")
@ -440,7 +441,9 @@ def parse_args(argv):
subparsers.add_parser('version', help="Print the esphomeyaml version and exit.") subparsers.add_parser('version', help="Print the esphomeyaml version and exit.")
subparsers.add_parser('clean', help="Delete all temporary build files.") clean = subparsers.add_parser('clean', help="Delete all temporary build files.")
clean.add_argument('--dashboard', help="Internal flag used by the dashboard",
action='store_true')
dashboard = subparsers.add_parser('dashboard', dashboard = subparsers.add_parser('dashboard',
help="Create a simple web server for a dashboard.") help="Create a simple web server for a dashboard.")
@ -455,14 +458,20 @@ def parse_args(argv):
"add-on.", "add-on.",
action="store_true") action="store_true")
subparsers.add_parser('hass-config', help="Dump the configuration entries that should be added" hass_config = subparsers.add_parser('hass-config',
"to Home Assistant when not using MQTT discovery.") help="Dump the configuration entries that should be added "
"to Home Assistant when not using MQTT discovery.")
hass_config.add_argument('--dashboard', help="Internal flag used by the dashboard",
action='store_true')
return parser.parse_args(argv[1:]) return parser.parse_args(argv[1:])
def run_esphomeyaml(argv): def run_esphomeyaml(argv):
args = parse_args(argv) args = parse_args(argv)
if hasattr(args, 'dashboard'):
core.FROM_DASHBOARD = args.dashboard
setup_log(args.verbose) setup_log(args.verbose)
if args.command in PRE_CONFIG_ACTIONS: if args.command in PRE_CONFIG_ACTIONS:
try: try:

View File

@ -59,7 +59,7 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend({
def validate(config): def validate(config):
if CONF_PASSWORD in config and CONF_SSID not in config: if CONF_PASSWORD in config and CONF_SSID not in config:
raise vol.Invalid("Cannot have WiFi password without SSID!") raise vol.Invalid("Cannot have WiFi password without SSID!")
if CONF_SSID not in config and CONF_AP not in config: if (CONF_SSID not in config) and (CONF_AP not in config):
raise vol.Invalid("Please specify at least an SSID or an Access Point " raise vol.Invalid("Please specify at least an SSID or an Access Point "
"to create.") "to create.")
return config return config

View File

@ -2,10 +2,10 @@ from __future__ import print_function
from collections import OrderedDict from collections import OrderedDict
import importlib import importlib
import json
import logging import logging
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error
from esphomeyaml import core, core_config, yaml_util from esphomeyaml import core, core_config, yaml_util
from esphomeyaml.const import CONF_ESPHOMEYAML, CONF_PLATFORM, CONF_WIFI, ESP_PLATFORMS from esphomeyaml.const import CONF_ESPHOMEYAML, CONF_PLATFORM, CONF_WIFI, ESP_PLATFORMS
@ -268,22 +268,53 @@ def validate_config(config):
REQUIRED = ['esphomeyaml', 'wifi'] REQUIRED = ['esphomeyaml', 'wifi']
def _format_config_error(ex, domain, config): def _nested_getitem(data, path):
message = u"Invalid config for [{}]: ".format(domain) for item_index in path:
try:
data = data[item_index]
except (KeyError, IndexError, TypeError):
return None
return data
def _format_path(path):
return u'->'.join(unicode(m) for m in path)
def humanize_error(config, validation_error):
offending_item_summary = _nested_getitem(config, validation_error.path)
if isinstance(offending_item_summary, dict):
offending_item_summary = json.dumps(offending_item_summary)
return u'{}. Got {}'.format(validation_error, offending_item_summary)
def _format_config_error(ex, domain, config, recursion=False):
message = u"" if recursion else u"Invalid config for [{}]: ".format(domain)
if isinstance(ex, vol.MultipleInvalid):
return color('red', message + u'\n'.join(sorted(
_format_config_error(sub_error, domain, config, recursion=True)
for sub_error in ex.errors
)))
if u'extra keys not allowed' in ex.error_message: if u'extra keys not allowed' in ex.error_message:
message += u'[{}] is an invalid option for [{}]. Check: {}->{}.' \ message += u'[{}] is an invalid option for [{}].' \
.format(ex.path[-1], domain, domain, .format(ex.path[-1], domain)
u'->'.join(str(m) for m in ex.path)) elif u'required key not provided' in ex.error_message:
message += u"'{}' is a required option for [{}]." \
u"".format(ex.path[-1], domain)
else: else:
message += u'{}.'.format(humanize_error(config, ex)) message += u'{}.'.format(humanize_error(config, ex))
message += u' Check {}->{}.'.format(domain, _format_path(ex.path))
message = color('red', message)
if isinstance(config, list): if isinstance(config, list):
return message return message
domain_config = config.get(domain, config) domain_config = config.get(domain, config)
message += u" (See {}, line {}). ".format( message += color('cyan', u" (See {}, line {}). ".format(
getattr(domain_config, '__config_file__', '?'), getattr(domain_config, '__config_file__', '?'),
getattr(domain_config, '__line__', '?')) getattr(domain_config, '__line__', '?')))
return message return message
@ -316,7 +347,7 @@ def line_info(obj, **kwargs):
return '?' return '?'
def dump_dict(layer, indent_count=3, listi=False, **kwargs): def dump_dict(layer, indent_count=0, listi=False, **kwargs):
def sort_dict_key(val): def sort_dict_key(val):
"""Return the dict key for sorting.""" """Return the dict key for sorting."""
key = str.lower(val[0]) key = str.lower(val[0])
@ -358,9 +389,7 @@ def read_config():
if excepts: if excepts:
safe_print(color('bold_white', u"Failed config")) safe_print(color('bold_white', u"Failed config"))
for domain, config in excepts.iteritems(): for domain, config in excepts.iteritems():
safe_print(u' {} {}'.format(color('bold_red', domain + u':'), safe_print(color('bold_red', domain + u':'))
color('red', '', reset='red'))) dump_dict(config)
dump_dict(config, reset='red')
safe_print(color('reset'))
return None return None
return OrderedDict(res) return OrderedDict(res)

View File

@ -398,3 +398,4 @@ CORE = EsphomeyamlCore()
ConfigType = Dict[str, Any] ConfigType = Dict[str, Any]
CoreType = EsphomeyamlCore CoreType = EsphomeyamlCore
FROM_DASHBOARD = False

View File

@ -105,7 +105,7 @@ class EsphomeyamlLogsHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message): def build_command(self, message):
js = json.loads(message) js = json.loads(message)
config_file = CONFIG_DIR + '/' + js['configuration'] config_file = CONFIG_DIR + '/' + js['configuration']
return ["esphomeyaml", config_file, "logs", '--serial-port', js["port"], '--escape'] return ["esphomeyaml", config_file, "logs", '--serial-port', js["port"], '--dashboard']
class EsphomeyamlRunHandler(EsphomeyamlCommandWebSocket): class EsphomeyamlRunHandler(EsphomeyamlCommandWebSocket):
@ -113,42 +113,42 @@ class EsphomeyamlRunHandler(EsphomeyamlCommandWebSocket):
js = json.loads(message) js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration']) config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "run", '--upload-port', js["port"], return ["esphomeyaml", config_file, "run", '--upload-port', js["port"],
'--escape', '--use-esptoolpy'] '--dashboard']
class EsphomeyamlCompileHandler(EsphomeyamlCommandWebSocket): class EsphomeyamlCompileHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message): def build_command(self, message):
js = json.loads(message) js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration']) config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "compile"] return ["esphomeyaml", config_file, "compile", '--dashboard']
class EsphomeyamlValidateHandler(EsphomeyamlCommandWebSocket): class EsphomeyamlValidateHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message): def build_command(self, message):
js = json.loads(message) js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration']) config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "config"] return ["esphomeyaml", config_file, "config", '--dashboard']
class EsphomeyamlCleanMqttHandler(EsphomeyamlCommandWebSocket): class EsphomeyamlCleanMqttHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message): def build_command(self, message):
js = json.loads(message) js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration']) config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "clean-mqtt"] return ["esphomeyaml", config_file, "clean-mqtt", '--dashboard']
class EsphomeyamlCleanHandler(EsphomeyamlCommandWebSocket): class EsphomeyamlCleanHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message): def build_command(self, message):
js = json.loads(message) js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration']) config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "clean"] return ["esphomeyaml", config_file, "clean", '--dashboard']
class EsphomeyamlHassConfigHandler(EsphomeyamlCommandWebSocket): class EsphomeyamlHassConfigHandler(EsphomeyamlCommandWebSocket):
def build_command(self, message): def build_command(self, message):
js = json.loads(message) js = json.loads(message)
config_file = os.path.join(CONFIG_DIR, js['configuration']) config_file = os.path.join(CONFIG_DIR, js['configuration'])
return ["esphomeyaml", config_file, "hass-config"] return ["esphomeyaml", config_file, "hass-config", '--dashboard']
class SerialPortRequestHandler(BaseHandler): class SerialPortRequestHandler(BaseHandler):

View File

@ -5,19 +5,19 @@ document.addEventListener('DOMContentLoaded', () => {
const colorReplace = (input) => { const colorReplace = (input) => {
input = input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); input = input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
input = input.replace(/\\033\[(?:0;)?31m/g, '<span class="e">'); input = input.replace(/\\033\[(?:0;)?31m/g, '<span class="e">');
input = input.replace(/\\033\[(?:1;)?31m/g, '<span class="e bold">'); input = input.replace(/\\033\[(?:0?1;)?31m/g, '<span class="e bold">');
input = input.replace(/\\033\[(?:0;)?32m/g, '<span class="i">'); input = input.replace(/\\033\[(?:0;)?32m/g, '<span class="i">');
input = input.replace(/\\033\[(?:1;)?32m/g, '<span class="i bold">'); input = input.replace(/\\033\[(?:0?1;)?32m/g, '<span class="i bold">');
input = input.replace(/\\033\[(?:0;)?33m/g, '<span class="w">'); input = input.replace(/\\033\[(?:0;)?33m/g, '<span class="w">');
input = input.replace(/\\033\[(?:1;)?33m/g, '<span class="w bold">'); input = input.replace(/\\033\[(?:0?1;)?33m/g, '<span class="w bold">');
input = input.replace(/\\033\[(?:0;)?35m/g, '<span class="c">'); input = input.replace(/\\033\[(?:0;)?35m/g, '<span class="c">');
input = input.replace(/\\033\[(?:1;)?35m/g, '<span class="c bold">'); input = input.replace(/\\033\[(?:0?1;)?35m/g, '<span class="c bold">');
input = input.replace(/\\033\[(?:0;)?36m/g, '<span class="d">'); input = input.replace(/\\033\[(?:0;)?36m/g, '<span class="d">');
input = input.replace(/\\033\[(?:1;)?36m/g, '<span class="d bold">'); input = input.replace(/\\033\[(?:0?1;)?36m/g, '<span class="d bold">');
input = input.replace(/\\033\[(?:0;)?37m/g, '<span class="v">'); input = input.replace(/\\033\[(?:0;)?37m/g, '<span class="v">');
input = input.replace(/\\033\[(?:1;)?37m/g, '<span class="v bold">'); input = input.replace(/\\033\[(?:0?1;)?37m/g, '<span class="v bold">');
input = input.replace(/\\033\[(?:0;)?38m/g, '<span class="vv">'); input = input.replace(/\\033\[(?:0;)?38m/g, '<span class="vv">');
input = input.replace(/\\033\[(?:1;)?38m/g, '<span class="vv bold">'); input = input.replace(/\\033\[(?:0?1;)?38m/g, '<span class="vv bold">');
input = input.replace(/\\033\[0m/g, '</span>'); input = input.replace(/\\033\[0m/g, '</span>');
return input; return input;
@ -26,50 +26,50 @@ return input;
let configuration = ""; let configuration = "";
let wsProtocol = "ws:"; let wsProtocol = "ws:";
if (window.location.protocol === "https:") { if (window.location.protocol === "https:") {
wsProtocol = 'wss:'; wsProtocol = 'wss:';
} }
const wsUrl = wsProtocol + '//' + window.location.hostname + ':' + window.location.port; const wsUrl = wsProtocol + '//' + window.location.hostname + ':' + window.location.port;
let isFetchingPing = false; let isFetchingPing = false;
const fetchPing = () => { const fetchPing = () => {
if (isFetchingPing) if (isFetchingPing)
return; return;
isFetchingPing = true; isFetchingPing = true;
fetch('/ping', {credentials: "same-origin"}).then(res => res.json()) fetch('/ping', {credentials: "same-origin"}).then(res => res.json())
.then(response => { .then(response => {
for (let filename in response) { for (let filename in response) {
let node = document.querySelector(`.status-indicator[data-node="${filename}"]`); let node = document.querySelector(`.status-indicator[data-node="${filename}"]`);
if (node === null) if (node === null)
continue; continue;
let status = response[filename]; let status = response[filename];
let klass; let klass;
if (status === null) { if (status === null) {
klass = 'unknown'; klass = 'unknown';
} else if (status === true) { } else if (status === true) {
klass = 'online'; klass = 'online';
node.setAttribute('data-last-connected', Date.now().toString()); node.setAttribute('data-last-connected', Date.now().toString());
} else if (node.hasAttribute('data-last-connected')) { } else if (node.hasAttribute('data-last-connected')) {
const attr = parseInt(node.getAttribute('data-last-connected')); const attr = parseInt(node.getAttribute('data-last-connected'));
if (Date.now() - attr <= 5000) { if (Date.now() - attr <= 5000) {
klass = 'not-responding'; klass = 'not-responding';
} else {
klass = 'offline';
}
} else { } else {
klass = 'offline'; klass = 'offline';
} }
} else {
klass = 'offline'; if (node.classList.contains(klass))
continue;
node.classList.remove('unknown', 'online', 'offline', 'not-responding');
node.classList.add(klass);
} }
if (node.classList.contains(klass)) isFetchingPing = false;
continue; });
node.classList.remove('unknown', 'online', 'offline', 'not-responding');
node.classList.add(klass);
}
isFetchingPing = false;
});
}; };
setInterval(fetchPing, 2000); setInterval(fetchPing, 2000);
fetchPing(); fetchPing();
@ -78,53 +78,53 @@ const portSelect = document.querySelector('.nav-wrapper select');
let ports = []; let ports = [];
const fetchSerialPorts = (begin=false) => { const fetchSerialPorts = (begin=false) => {
fetch('/serial-ports', {credentials: "same-origin"}).then(res => res.json()) fetch('/serial-ports', {credentials: "same-origin"}).then(res => res.json())
.then(response => { .then(response => {
if (ports.length === response.length) { if (ports.length === response.length) {
let allEqual = true; let allEqual = true;
for (let i = 0; i < response.length; i++) {
if (ports[i].port !== response[i].port) {
allEqual = false;
break;
}
}
if (allEqual)
return;
}
const hasNewPort = response.length >= ports.length;
ports = response;
const inst = M.FormSelect.getInstance(portSelect);
if (inst !== undefined) {
inst.destroy();
}
portSelect.innerHTML = "";
const prevSelected = getUploadPort();
for (let i = 0; i < response.length; i++) { for (let i = 0; i < response.length; i++) {
if (ports[i].port !== response[i].port) { const val = response[i];
allEqual = false; if (val.port === prevSelected) {
break; portSelect.innerHTML += `<option value="${val.port}" selected>${val.port} (${val.desc})</option>`;
} else {
portSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
} }
} }
if (allEqual)
return;
}
const hasNewPort = response.length >= ports.length;
ports = response; M.FormSelect.init(portSelect, {});
if (!begin && hasNewPort)
const inst = M.FormSelect.getInstance(portSelect); M.toast({html: "Discovered new serial port."});
if (inst !== undefined) { });
inst.destroy();
}
portSelect.innerHTML = "";
const prevSelected = getUploadPort();
for (let i = 0; i < response.length; i++) {
const val = response[i];
if (val.port === prevSelected) {
portSelect.innerHTML += `<option value="${val.port}" selected>${val.port} (${val.desc})</option>`;
} else {
portSelect.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
}
}
M.FormSelect.init(portSelect, {});
if (!begin && hasNewPort)
M.toast({html: "Discovered new serial port."});
});
}; };
const getUploadPort = () => { const getUploadPort = () => {
const inst = M.FormSelect.getInstance(portSelect); const inst = M.FormSelect.getInstance(portSelect);
if (inst === undefined) { if (inst === undefined) {
return "OTA"; return "OTA";
} }
inst._setSelectedStates(); inst._setSelectedStates();
return inst.getSelectedValues()[0]; return inst.getSelectedValues()[0];
}; };
setInterval(fetchSerialPorts, 5000); setInterval(fetchSerialPorts, 5000);
fetchSerialPorts(true); fetchSerialPorts(true);
@ -132,347 +132,348 @@ fetchSerialPorts(true);
const logsModalElem = document.getElementById("modal-logs"); const logsModalElem = document.getElementById("modal-logs");
document.querySelectorAll(".action-show-logs").forEach((showLogs) => { document.querySelectorAll(".action-show-logs").forEach((showLogs) => {
showLogs.addEventListener('click', (e) => { showLogs.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node'); configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(logsModalElem); const modalInstance = M.Modal.getInstance(logsModalElem);
const log = logsModalElem.querySelector(".log"); const log = logsModalElem.querySelector(".log");
log.innerHTML = ""; log.innerHTML = "";
const stopLogsButton = logsModalElem.querySelector(".stop-logs"); const stopLogsButton = logsModalElem.querySelector(".stop-logs");
let stopped = false; let stopped = false;
stopLogsButton.innerHTML = "Stop"; stopLogsButton.innerHTML = "Stop";
modalInstance.open(); modalInstance.open();
const filenameField = logsModalElem.querySelector('.filename'); const filenameField = logsModalElem.querySelector('.filename');
filenameField.innerHTML = configuration; filenameField.innerHTML = configuration;
const logSocket = new WebSocket(wsUrl + "/logs"); const logSocket = new WebSocket(wsUrl + "/logs");
logSocket.addEventListener('message', (event) => { logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.event === "line") { if (data.event === "line") {
const msg = data.data; const msg = data.data;
log.innerHTML += colorReplace(msg); log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") { } else if (data.event === "exit") {
if (data.code === 0) { if (data.code === 0) {
M.toast({html: "Program exited successfully."}); M.toast({html: "Program exited successfully."});
} else { } else {
M.toast({html: `Program failed with code ${data.code}`}); M.toast({html: `Program failed with code ${data.code}`});
}
stopLogsButton.innerHTML = "Close";
stopped = true;
} }
});
stopLogsButton.innerHTML = "Close"; logSocket.addEventListener('open', () => {
stopped = true; const msg = JSON.stringify({configuration: configuration, port: getUploadPort()});
} logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
}); });
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration, port: getUploadPort()});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
}); });
const uploadModalElem = document.getElementById("modal-upload"); const uploadModalElem = document.getElementById("modal-upload");
document.querySelectorAll(".action-upload").forEach((upload) => { document.querySelectorAll(".action-upload").forEach((upload) => {
upload.addEventListener('click', (e) => { upload.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node'); configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(uploadModalElem); const modalInstance = M.Modal.getInstance(uploadModalElem);
const log = uploadModalElem.querySelector(".log"); const log = uploadModalElem.querySelector(".log");
log.innerHTML = ""; log.innerHTML = "";
const stopLogsButton = uploadModalElem.querySelector(".stop-logs"); const stopLogsButton = uploadModalElem.querySelector(".stop-logs");
let stopped = false; let stopped = false;
stopLogsButton.innerHTML = "Stop"; stopLogsButton.innerHTML = "Stop";
modalInstance.open(); modalInstance.open();
const filenameField = uploadModalElem.querySelector('.filename'); const filenameField = uploadModalElem.querySelector('.filename');
filenameField.innerHTML = configuration; filenameField.innerHTML = configuration;
const logSocket = new WebSocket(wsUrl + "/run"); const logSocket = new WebSocket(wsUrl + "/run");
logSocket.addEventListener('message', (event) => { logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.event === "line") { if (data.event === "line") {
const msg = data.data; const msg = data.data;
log.innerHTML += colorReplace(msg); log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") { } else if (data.event === "exit") {
if (data.code === 0) { if (data.code === 0) {
M.toast({html: "Program exited successfully."}); M.toast({html: "Program exited successfully."});
} else { } else {
M.toast({html: `Program failed with code ${data.code}`}); M.toast({html: `Program failed with code ${data.code}`});
}
stopLogsButton.innerHTML = "Close";
stopped = true;
} }
});
stopLogsButton.innerHTML = "Close"; logSocket.addEventListener('open', () => {
stopped = true; const msg = JSON.stringify({configuration: configuration, port: getUploadPort()});
} logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
}); });
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration, port: getUploadPort()});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
}); });
const validateModalElem = document.getElementById("modal-validate"); const validateModalElem = document.getElementById("modal-validate");
document.querySelectorAll(".action-validate").forEach((upload) => { document.querySelectorAll(".action-validate").forEach((upload) => {
upload.addEventListener('click', (e) => { upload.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node'); configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(validateModalElem); const modalInstance = M.Modal.getInstance(validateModalElem);
const log = validateModalElem.querySelector(".log"); const log = validateModalElem.querySelector(".log");
log.innerHTML = ""; log.innerHTML = "";
const stopLogsButton = validateModalElem.querySelector(".stop-logs"); const stopLogsButton = validateModalElem.querySelector(".stop-logs");
let stopped = false; let stopped = false;
stopLogsButton.innerHTML = "Stop"; stopLogsButton.innerHTML = "Stop";
modalInstance.open(); modalInstance.open();
const filenameField = validateModalElem.querySelector('.filename'); const filenameField = validateModalElem.querySelector('.filename');
filenameField.innerHTML = configuration; filenameField.innerHTML = configuration;
const logSocket = new WebSocket(wsUrl + "/validate"); const logSocket = new WebSocket(wsUrl + "/validate");
logSocket.addEventListener('message', (event) => { logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.event === "line") { if (data.event === "line") {
const msg = data.data; const msg = data.data;
log.innerHTML += colorReplace(msg); log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") { } else if (data.event === "exit") {
if (data.code === 0) { if (data.code === 0) {
M.toast({ M.toast({
html: `<code class="inlinecode">${configuration}</code> is valid 👍`, html: `<code class="inlinecode">${configuration}</code> is valid 👍`,
displayLength: 5000, displayLength: 5000,
}); });
} else { } else {
M.toast({ M.toast({
html: `<code class="inlinecode">${configuration}</code> is invalid 😕`, html: `<code class="inlinecode">${configuration}</code> is invalid 😕`,
displayLength: 5000, displayLength: 5000,
}); });
}
stopLogsButton.innerHTML = "Close";
stopped = true;
} }
});
stopLogsButton.innerHTML = "Close"; logSocket.addEventListener('open', () => {
stopped = true; const msg = JSON.stringify({configuration: configuration});
} logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
}); });
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
}); });
const compileModalElem = document.getElementById("modal-compile"); const compileModalElem = document.getElementById("modal-compile");
const downloadButton = compileModalElem.querySelector('.download-binary'); const downloadButton = compileModalElem.querySelector('.download-binary');
document.querySelectorAll(".action-compile").forEach((upload) => { document.querySelectorAll(".action-compile").forEach((upload) => {
upload.addEventListener('click', (e) => { upload.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node'); configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(compileModalElem); const modalInstance = M.Modal.getInstance(compileModalElem);
const log = compileModalElem.querySelector(".log"); const log = compileModalElem.querySelector(".log");
log.innerHTML = ""; log.innerHTML = "";
const stopLogsButton = compileModalElem.querySelector(".stop-logs"); const stopLogsButton = compileModalElem.querySelector(".stop-logs");
let stopped = false; let stopped = false;
stopLogsButton.innerHTML = "Stop"; stopLogsButton.innerHTML = "Stop";
downloadButton.classList.add('disabled'); downloadButton.classList.add('disabled');
modalInstance.open(); modalInstance.open();
const filenameField = compileModalElem.querySelector('.filename'); const filenameField = compileModalElem.querySelector('.filename');
filenameField.innerHTML = configuration; filenameField.innerHTML = configuration;
const logSocket = new WebSocket(wsUrl + "/compile"); const logSocket = new WebSocket(wsUrl + "/compile");
logSocket.addEventListener('message', (event) => { logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.event === "line") { if (data.event === "line") {
const msg = data.data; const msg = data.data;
log.innerHTML += colorReplace(msg); log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") { } else if (data.event === "exit") {
if (data.code === 0) { if (data.code === 0) {
M.toast({html: "Program exited successfully."}); M.toast({html: "Program exited successfully."});
downloadButton.classList.remove('disabled'); downloadButton.classList.remove('disabled');
} else { } else {
M.toast({html: `Program failed with code ${data.code}`}); M.toast({html: `Program failed with code ${data.code}`});
}
stopLogsButton.innerHTML = "Close";
stopped = true;
} }
});
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
});
stopLogsButton.innerHTML = "Close";
stopped = true;
}
});
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
});
downloadButton.addEventListener('click', () => { downloadButton.addEventListener('click', () => {
const link = document.createElement("a"); const link = document.createElement("a");
link.download = name; link.download = name;
link.href = '/download.bin?configuration=' + encodeURIComponent(configuration); link.href = '/download.bin?configuration=' + encodeURIComponent(configuration);
link.click(); link.click();
}); });
const cleanMqttModalElem = document.getElementById("modal-clean-mqtt"); const cleanMqttModalElem = document.getElementById("modal-clean-mqtt");
document.querySelectorAll(".action-clean-mqtt").forEach((btn) => { document.querySelectorAll(".action-clean-mqtt").forEach((btn) => {
btn.addEventListener('click', (e) => { btn.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node'); configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(cleanMqttModalElem); const modalInstance = M.Modal.getInstance(cleanMqttModalElem);
const log = cleanMqttModalElem.querySelector(".log"); const log = cleanMqttModalElem.querySelector(".log");
log.innerHTML = ""; log.innerHTML = "";
const stopLogsButton = cleanMqttModalElem.querySelector(".stop-logs"); const stopLogsButton = cleanMqttModalElem.querySelector(".stop-logs");
let stopped = false; let stopped = false;
stopLogsButton.innerHTML = "Stop"; stopLogsButton.innerHTML = "Stop";
modalInstance.open(); modalInstance.open();
const filenameField = cleanMqttModalElem.querySelector('.filename'); const filenameField = cleanMqttModalElem.querySelector('.filename');
filenameField.innerHTML = configuration; filenameField.innerHTML = configuration;
const logSocket = new WebSocket(wsUrl + "/clean-mqtt"); const logSocket = new WebSocket(wsUrl + "/clean-mqtt");
logSocket.addEventListener('message', (event) => { logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.event === "line") { if (data.event === "line") {
const msg = data.data; const msg = data.data;
log.innerHTML += colorReplace(msg); log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") { } else if (data.event === "exit") {
stopLogsButton.innerHTML = "Close"; stopLogsButton.innerHTML = "Close";
stopped = true; stopped = true;
} }
});
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
}); });
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
}); });
const cleanModalElem = document.getElementById("modal-clean"); const cleanModalElem = document.getElementById("modal-clean");
document.querySelectorAll(".action-clean").forEach((btn) => { document.querySelectorAll(".action-clean").forEach((btn) => {
btn.addEventListener('click', (e) => { btn.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node'); configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(cleanModalElem); const modalInstance = M.Modal.getInstance(cleanModalElem);
const log = cleanModalElem.querySelector(".log"); const log = cleanModalElem.querySelector(".log");
log.innerHTML = ""; log.innerHTML = "";
const stopLogsButton = cleanModalElem.querySelector(".stop-logs"); const stopLogsButton = cleanModalElem.querySelector(".stop-logs");
let stopped = false; let stopped = false;
stopLogsButton.innerHTML = "Stop"; stopLogsButton.innerHTML = "Stop";
modalInstance.open(); modalInstance.open();
const filenameField = cleanModalElem.querySelector('.filename'); const filenameField = cleanModalElem.querySelector('.filename');
filenameField.innerHTML = configuration; filenameField.innerHTML = configuration;
const logSocket = new WebSocket(wsUrl + "/clean"); const logSocket = new WebSocket(wsUrl + "/clean");
logSocket.addEventListener('message', (event) => { logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.event === "line") { if (data.event === "line") {
const msg = data.data; const msg = data.data;
log.innerHTML += colorReplace(msg); log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") { } else if (data.event === "exit") {
if (data.code === 0) { if (data.code === 0) {
M.toast({html: "Program exited successfully."}); M.toast({html: "Program exited successfully."});
downloadButton.classList.remove('disabled'); downloadButton.classList.remove('disabled');
} else { } else {
M.toast({html: `Program failed with code ${data.code}`}); M.toast({html: `Program failed with code ${data.code}`});
}
stopLogsButton.innerHTML = "Close";
stopped = true;
} }
stopLogsButton.innerHTML = "Close"; });
stopped = true; logSocket.addEventListener('open', () => {
} const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
}); });
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
}); });
const hassConfigModalElem = document.getElementById("modal-hass-config"); const hassConfigModalElem = document.getElementById("modal-hass-config");
document.querySelectorAll(".action-hass-config").forEach((btn) => { document.querySelectorAll(".action-hass-config").forEach((btn) => {
btn.addEventListener('click', (e) => { btn.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node'); configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(hassConfigModalElem); const modalInstance = M.Modal.getInstance(hassConfigModalElem);
const log = hassConfigModalElem.querySelector(".log"); const log = hassConfigModalElem.querySelector(".log");
log.innerHTML = ""; log.innerHTML = "";
const stopLogsButton = hassConfigModalElem.querySelector(".stop-logs"); const stopLogsButton = hassConfigModalElem.querySelector(".stop-logs");
let stopped = false; let stopped = false;
stopLogsButton.innerHTML = "Stop"; stopLogsButton.innerHTML = "Stop";
modalInstance.open(); modalInstance.open();
const filenameField = hassConfigModalElem.querySelector('.filename'); const filenameField = hassConfigModalElem.querySelector('.filename');
filenameField.innerHTML = configuration; filenameField.innerHTML = configuration;
const logSocket = new WebSocket(wsUrl + "/hass-config"); const logSocket = new WebSocket(wsUrl + "/hass-config");
logSocket.addEventListener('message', (event) => { logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.event === "line") { if (data.event === "line") {
const msg = data.data; const msg = data.data;
log.innerHTML += colorReplace(msg); log.innerHTML += colorReplace(msg);
} else if (data.event === "exit") { } else if (data.event === "exit") {
if (data.code === 0) { if (data.code === 0) {
M.toast({html: "Program exited successfully."}); M.toast({html: "Program exited successfully."});
downloadButton.classList.remove('disabled'); downloadButton.classList.remove('disabled');
} else { } else {
M.toast({html: `Program failed with code ${data.code}`}); M.toast({html: `Program failed with code ${data.code}`});
}
stopLogsButton.innerHTML = "Close";
stopped = true;
} }
stopLogsButton.innerHTML = "Close"; });
stopped = true; logSocket.addEventListener('open', () => {
} const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
}); });
logSocket.addEventListener('open', () => {
const msg = JSON.stringify({configuration: configuration});
logSocket.send(msg);
});
logSocket.addEventListener('close', () => {
if (!stopped) {
M.toast({html: 'Terminated process.'});
}
});
modalInstance.options.onCloseStart = () => {
logSocket.close();
};
});
}); });
const editModalElem = document.getElementById("modal-editor"); const editModalElem = document.getElementById("modal-editor");
@ -486,58 +487,59 @@ editor.session.setOption('tabSize', 2);
const saveButton = editModalElem.querySelector(".save-button"); const saveButton = editModalElem.querySelector(".save-button");
const saveEditor = () => { const saveEditor = () => {
fetch(`/edit?configuration=${configuration}`, { fetch(`/edit?configuration=${configuration}`, {
credentials: "same-origin", credentials: "same-origin",
method: "POST", method: "POST",
body: editor.getValue() body: editor.getValue()
}).then(res => res.text()).then(() => { }).then(res => res.text()).then(() => {
M.toast({ M.toast({
html: `Saved <code class="inlinecode">${configuration}</code>` html: `Saved <code class="inlinecode">${configuration}</code>`
});
}); });
});
}; };
editor.commands.addCommand({ editor.commands.addCommand({
name: 'saveCommand', name: 'saveCommand',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: saveEditor, exec: saveEditor,
readOnly: false readOnly: false
}); });
saveButton.addEventListener('click', saveEditor); saveButton.addEventListener('click', saveEditor);
document.querySelectorAll(".action-edit").forEach((btn) => { document.querySelectorAll(".action-edit").forEach((btn) => {
btn.addEventListener('click', (e) => { btn.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node'); configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(editModalElem); const modalInstance = M.Modal.getInstance(editModalElem);
const filenameField = editModalElem.querySelector('.filename'); const filenameField = editModalElem.querySelector('.filename');
filenameField.innerHTML = configuration; filenameField.innerHTML = configuration;
fetch(`/edit?configuration=${configuration}`, {credentials: "same-origin"}) fetch(`/edit?configuration=${configuration}`, {credentials: "same-origin"})
.then(res => res.text()).then(response => { .then(res => res.text()).then(response => {
editor.setValue(response, -1); editor.setValue(response, -1);
});
modalInstance.open();
}); });
modalInstance.open();
});
}); });
const modalSetupElem = document.getElementById("modal-wizard"); const modalSetupElem = document.getElementById("modal-wizard");
const setupWizardStart = document.getElementById('setup-wizard-start'); const setupWizardStart = document.getElementById('setup-wizard-start');
const startWizard = () => { const startWizard = () => {
const modalInstance = M.Modal.getInstance(modalSetupElem); const modalInstance = M.Modal.getInstance(modalSetupElem);
modalInstance.open(); modalInstance.open();
modalInstance.options.onCloseStart = () => { modalInstance.options.onCloseStart = () => {
};
$('.stepper').activateStepper({
linearStepsNavigation: false,
autoFocusInput: true,
autoFormCreation: true,
showFeedbackLoader: true,
parallel: false
});
}; };
$('.stepper').activateStepper({
linearStepsNavigation: false,
autoFocusInput: true,
autoFormCreation: true,
showFeedbackLoader: true,
parallel: false
});
};
setupWizardStart.addEventListener('click', startWizard); setupWizardStart.addEventListener('click', startWizard);

View File

@ -49,12 +49,18 @@ def cpp_string_escape(string, encoding='utf-8'):
return '"' + result + '"' return '"' + result + '"'
def color(the_color, message='', reset=None): def color(the_color, message=''):
"""Color helper.""" from esphomeyaml import core
from colorlog.escape_codes import escape_codes, parse_colors from colorlog.escape_codes import escape_codes, parse_colors
if not message: if not message:
return parse_colors(the_color) res = parse_colors(the_color)
return parse_colors(the_color) + message + escape_codes[reset or 'reset'] else:
res = parse_colors(the_color) + message + escape_codes['reset']
if core.FROM_DASHBOARD:
res = res.replace('\033', '\\033')
return res
def run_system_command(*args): def run_system_command(*args):

View File

@ -9,6 +9,7 @@ import sys
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
from esphomeyaml import core
from esphomeyaml.const import CONF_BROKER, CONF_DISCOVERY_PREFIX, CONF_ESPHOMEYAML, \ from esphomeyaml.const import CONF_BROKER, CONF_DISCOVERY_PREFIX, CONF_ESPHOMEYAML, \
CONF_LOG_TOPIC, CONF_MQTT, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SSL_FINGERPRINTS, \ CONF_LOG_TOPIC, CONF_MQTT, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SSL_FINGERPRINTS, \
CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME CONF_TOPIC, CONF_TOPIC_PREFIX, CONF_USERNAME
@ -54,7 +55,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id)
return 0 return 0
def show_logs(config, topic=None, username=None, password=None, client_id=None, escape=False): def show_logs(config, topic=None, username=None, password=None, client_id=None):
if topic is not None: if topic is not None:
pass # already have topic pass # already have topic
elif CONF_MQTT in config: elif CONF_MQTT in config:
@ -73,7 +74,7 @@ def show_logs(config, topic=None, username=None, password=None, client_id=None,
def on_message(client, userdata, msg): def on_message(client, userdata, msg):
time = datetime.now().time().strftime(u'[%H:%M:%S]') time = datetime.now().time().strftime(u'[%H:%M:%S]')
message = time + msg.payload message = time + msg.payload
if escape: if core.FROM_DASHBOARD:
message = message.replace('\033', '\\033') message = message.replace('\033', '\\033')
safe_print(message) safe_print(message)