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:
parent
8587e7ad74
commit
41d5dcded1
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -398,3 +398,4 @@ CORE = EsphomeyamlCore()
|
|||||||
|
|
||||||
ConfigType = Dict[str, Any]
|
ConfigType = Dict[str, Any]
|
||||||
CoreType = EsphomeyamlCore
|
CoreType = EsphomeyamlCore
|
||||||
|
FROM_DASHBOARD = False
|
||||||
|
@ -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):
|
||||||
|
@ -5,19 +5,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const colorReplace = (input) => {
|
const colorReplace = (input) => {
|
||||||
input = input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
input = input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||||
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);
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user