Changes for 1.12 (#482)

* Update setup scripts

* Add delete action, remove Hass config command

* Update esphome.js

* Lint
This commit is contained in:
Otto Winter
2019-03-16 22:24:26 +01:00
committed by GitHub
parent 0ccfe33427
commit d332e491ad
107 changed files with 156 additions and 837 deletions
+72 -42
View File
@@ -3,11 +3,13 @@ from __future__ import print_function
import codecs
import collections
import hashlib
import hmac
import json
import logging
import multiprocessing
import os
import shutil
import subprocess
import threading
@@ -28,7 +30,7 @@ from esphome.__main__ import get_serial_ports
from esphome.helpers import mkdir_p, get_bool_env, run_system_command
from esphome.py_compat import IS_PY2
from esphome.storage_json import EsphomeStorageJSON, StorageJSON, \
esphome_storage_path, ext_storage_path
esphome_storage_path, ext_storage_path, trash_storage_path
from esphome.util import shlex_quote
# pylint: disable=unused-import, wrong-import-order
@@ -64,6 +66,27 @@ def template_args():
}
def authenticated(func):
def decorator(self, *args, **kwargs):
if not self.is_authenticated():
self.redirect(RELATIVE_URL + 'login')
return None
return func(self, *args, **kwargs)
return decorator
def bind_config(func):
def decorator(self, *args, **kwargs):
configuration = self.get_argument('configuration')
if not is_allowed(configuration):
self.set_status(500)
return None
kwargs = kwargs.copy()
kwargs['configuration'] = configuration
return func(self, *args, **kwargs)
return decorator
# pylint: disable=abstract-method
class BaseHandler(tornado.web.RequestHandler):
def is_authenticated(self):
@@ -178,10 +201,8 @@ class EsphomeHassConfigHandler(EsphomeCommandWebSocket):
class SerialPortRequestHandler(BaseHandler):
@authenticated
def get(self):
if not self.is_authenticated():
self.redirect(RELATIVE_URL + 'login')
return
ports = get_serial_ports()
data = []
for port, desc in ports:
@@ -198,12 +219,10 @@ class SerialPortRequestHandler(BaseHandler):
class WizardRequestHandler(BaseHandler):
@authenticated
def post(self):
from esphome import wizard
if not self.is_authenticated():
self.redirect(RELATIVE_URL + 'login')
return
kwargs = {k: ''.join(v) for k, v in self.request.arguments.items()}
destination = os.path.join(CONFIG_DIR, kwargs['name'] + '.yaml')
wizard.wizard_write(path=destination, **kwargs)
@@ -211,13 +230,10 @@ class WizardRequestHandler(BaseHandler):
class DownloadBinaryRequestHandler(BaseHandler):
def get(self):
if not self.is_authenticated():
self.redirect(RELATIVE_URL + 'login')
return
@authenticated
@bind_config
def get(self, configuration=None):
# pylint: disable=no-value-for-parameter
configuration = self.get_argument('configuration')
storage_path = ext_storage_path(CONFIG_DIR, configuration)
storage_json = StorageJSON.load(storage_path)
if storage_json is None:
@@ -315,11 +331,8 @@ class DashboardEntry(object):
class MainRequestHandler(BaseHandler):
@authenticated
def get(self):
if not self.is_authenticated():
self.redirect(RELATIVE_URL + 'login')
return
begin = bool(self.get_argument('begin', False))
entries = _list_dashboard_entries()
@@ -400,11 +413,8 @@ class PingStatusThread(threading.Thread):
class PingRequestHandler(BaseHandler):
@authenticated
def get(self):
if not self.is_authenticated():
self.redirect(RELATIVE_URL + 'login')
return
PING_REQUEST.set()
self.write(json.dumps(PING_RESULT))
@@ -414,34 +424,52 @@ def is_allowed(configuration):
class EditRequestHandler(BaseHandler):
def get(self):
if not self.is_authenticated():
self.redirect(RELATIVE_URL + 'login')
return
@authenticated
@bind_config
def get(self, configuration=None):
# pylint: disable=no-value-for-parameter
configuration = self.get_argument('configuration')
if not is_allowed(configuration):
self.set_status(401)
return
with open(os.path.join(CONFIG_DIR, configuration), 'r') as f:
content = f.read()
self.write(content)
def post(self):
if not self.is_authenticated():
self.redirect(RELATIVE_URL + 'login')
return
@authenticated
@bind_config
def post(self, configuration=None):
# pylint: disable=no-value-for-parameter
configuration = self.get_argument('configuration')
if not is_allowed(configuration):
self.set_status(401)
return
with open(os.path.join(CONFIG_DIR, configuration), 'wb') as f:
f.write(self.request.body)
self.set_status(200)
return
class DeleteRequestHandler(BaseHandler):
@authenticated
@bind_config
def post(self, configuration=None):
config_file = os.path.join(CONFIG_DIR, configuration)
storage_path = ext_storage_path(CONFIG_DIR, configuration)
storage_json = StorageJSON.load(storage_path)
if storage_json is None:
self.set_status(500)
return
name = storage_json.name
trash_path = trash_storage_path(CONFIG_DIR)
mkdir_p(trash_path)
shutil.move(config_file, os.path.join(trash_path, configuration))
# Delete build folder (if exists)
build_folder = os.path.join(CONFIG_DIR, name)
if build_folder is not None:
shutil.rmtree(build_folder, os.path.join(trash_path, name))
class UndoDeleteRequestHandler(BaseHandler):
@authenticated
@bind_config
def post(self, configuration=None):
config_file = os.path.join(CONFIG_DIR, configuration)
trash_path = trash_storage_path(CONFIG_DIR)
shutil.move(os.path.join(trash_path, configuration), config_file)
PING_RESULT = {} # type: dict
@@ -511,9 +539,9 @@ def get_static_file_url(name):
else:
path = os.path.join(static_path, name)
with open(path, 'rb') as f_handle:
hash_ = hash(f_handle.read()) & (2**32-1)
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
_STATIC_FILE_HASHES[name] = hash_
return RELATIVE_URL + u'static/{}?hash={:08X}'.format(name, hash_)
return RELATIVE_URL + u'static/{}?hash={}'.format(name, hash_)
def make_app(debug=False):
@@ -561,6 +589,8 @@ def make_app(debug=False):
(RELATIVE_URL + "download.bin", DownloadBinaryRequestHandler),
(RELATIVE_URL + "serial-ports", SerialPortRequestHandler),
(RELATIVE_URL + "ping", PingRequestHandler),
(RELATIVE_URL + "delete", DeleteRequestHandler),
(RELATIVE_URL + "undo-delete", UndoDeleteRequestHandler),
(RELATIVE_URL + "wizard.html", WizardRequestHandler),
(RELATIVE_URL + r"static/(.*)", StaticFileHandler, {'path': static_path}),
], **settings)
+19 -40
View File
@@ -590,51 +590,30 @@ document.querySelectorAll(".action-clean").forEach((btn) => {
});
});
const hassConfigModalElem = document.getElementById("modal-hass-config");
document.querySelectorAll(".action-hass-config").forEach((btn) => {
document.querySelectorAll(".action-delete").forEach((btn) => {
btn.addEventListener('click', (e) => {
configuration = e.target.getAttribute('data-node');
const modalInstance = M.Modal.getInstance(hassConfigModalElem);
const log = hassConfigModalElem.querySelector(".log");
log.innerHTML = "";
const colorState = initializeColorState();
const stopLogsButton = hassConfigModalElem.querySelector(".stop-logs");
let stopped = false;
stopLogsButton.innerHTML = "Stop";
modalInstance.open();
const filenameField = hassConfigModalElem.querySelector('.filename');
filenameField.innerHTML = configuration;
fetch(`${relative_url}delete?configuration=${configuration}`, {
credentials: "same-origin",
method: "POST",
}).then(res => res.text()).then(() => {
const toastHtml = `<span>Deleted <code class="inlinecode">${configuration}</code>
<button class="btn-flat toast-action">Undo</button></button>`;
const toast = M.toast({html: toastHtml});
const undoButton = toast.el.querySelector('.toast-action');
const logSocket = new WebSocket(wsUrl + "hass-config");
logSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.event === "line") {
colorReplace(log, colorState, data.data);
} else if (data.event === "exit") {
if (data.code === 0) {
M.toast({html: "Program exited successfully."});
downloadButton.classList.remove('disabled');
} else {
M.toast({html: `Program failed with code ${data.code}`});
}
stopLogsButton.innerHTML = "Close";
stopped = true;
}
document.querySelector(`.entry-row[data-node="${configuration}"]`).remove();
undoButton.addEventListener('click', () => {
fetch(`${relative_url}undo-delete?configuration=${configuration}`, {
credentials: "same-origin",
method: "POST",
}).then(res => res.text()).then(() => {
window.location.reload(false);
});
});
});
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();
};
});
});
+2 -12
View File
@@ -58,7 +58,7 @@
<main>
<div class="container">
{% for i, entry in enumerate(entries) %}
<div class="row">
<div class="row entry-row" data-node="{{ entry.filename }}">
<div class="col s12 m10 offset-m1 l12">
<div class="card horizontal">
<div class="card-image center-align hide-on-small-only">
@@ -93,7 +93,7 @@
<li><a class="action-clean-mqtt" data-node="{{ entry.filename }}">Clean MQTT</a></li>
<li><a class="action-clean" data-node="{{ entry.filename }}">Clean Build</a></li>
<li><a class="action-compile" data-node="{{ entry.filename }}">Compile</a></li>
<li><a class="action-hass-config" data-node="{{ entry.filename }}">HASS MQTT Configuration</a></li>
<li><a class="action-delete" data-node="{{ entry.filename }}">Delete</a></li>
</ul>
</div>
</div>
@@ -422,16 +422,6 @@
</div>
</div>
<div id="modal-hass-config" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Generate Home Assistant Configuration <code class="inlinecode filename"></code></h4>
<pre class="log"></pre>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat stop-logs">Stop</a>
</div>
</div>
<div id="modal-editor" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Edit <code class="inlinecode filename"></code></h4>