mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-06-04 03:48:29 +02:00
Dashboard node import and render in browser (#2374)
This commit is contained in:
@@ -41,7 +41,7 @@ from .util import password_hash
|
||||
# pylint: disable=unused-import, wrong-import-order
|
||||
from typing import Optional # noqa
|
||||
|
||||
from esphome.zeroconf import DashboardStatus, EsphomeZeroconf
|
||||
from esphome.zeroconf import DashboardImportDiscovery, DashboardStatus, EsphomeZeroconf
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -154,9 +154,6 @@ def is_authenticated(request_handler):
|
||||
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)
|
||||
@@ -363,8 +360,8 @@ class WizardRequestHandler(BaseHandler):
|
||||
from esphome import wizard
|
||||
|
||||
kwargs = {
|
||||
k: "".join(x.decode() for x in v)
|
||||
for k, v in self.request.arguments.items()
|
||||
k: v
|
||||
for k, v in json.loads(self.request.body.decode()).items()
|
||||
if k in ("name", "platform", "board", "ssid", "psk", "password")
|
||||
}
|
||||
kwargs["ota_password"] = secrets.token_hex(16)
|
||||
@@ -374,6 +371,29 @@ class WizardRequestHandler(BaseHandler):
|
||||
self.finish()
|
||||
|
||||
|
||||
class ImportRequestHandler(BaseHandler):
|
||||
@authenticated
|
||||
def post(self):
|
||||
from esphome.components.dashboard_import import import_config
|
||||
|
||||
args = json.loads(self.request.body.decode())
|
||||
try:
|
||||
name = args["name"]
|
||||
import_config(
|
||||
settings.rel_path(f"{name}.yaml"),
|
||||
name,
|
||||
args["project_name"],
|
||||
args["package_import_url"],
|
||||
)
|
||||
except FileExistsError:
|
||||
self.set_status(500)
|
||||
self.write("File already exists")
|
||||
return
|
||||
|
||||
self.set_status(200)
|
||||
self.finish()
|
||||
|
||||
|
||||
class DownloadBinaryRequestHandler(BaseHandler):
|
||||
@authenticated
|
||||
@bind_config
|
||||
@@ -469,15 +489,51 @@ class DashboardEntry:
|
||||
return self.storage.loaded_integrations
|
||||
|
||||
|
||||
class ListDevicesHandler(BaseHandler):
|
||||
@authenticated
|
||||
def get(self):
|
||||
entries = _list_dashboard_entries()
|
||||
self.set_header("content-type", "application/json")
|
||||
configured = {entry.name for entry in entries}
|
||||
self.write(
|
||||
json.dumps(
|
||||
{
|
||||
"configured": [
|
||||
{
|
||||
"name": entry.name,
|
||||
"configuration": entry.filename,
|
||||
"loaded_integrations": entry.loaded_integrations,
|
||||
"deployed_version": entry.update_old,
|
||||
"current_version": entry.update_new,
|
||||
"path": entry.path,
|
||||
"comment": entry.comment,
|
||||
"address": entry.address,
|
||||
"target_platform": entry.target_platform,
|
||||
}
|
||||
for entry in entries
|
||||
],
|
||||
"importable": [
|
||||
{
|
||||
"name": res.device_name,
|
||||
"package_import_url": res.package_import_url,
|
||||
"project_name": res.project_name,
|
||||
"project_version": res.project_version,
|
||||
}
|
||||
for res in IMPORT_RESULT.values()
|
||||
if res.device_name not in configured
|
||||
],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class MainRequestHandler(BaseHandler):
|
||||
@authenticated
|
||||
def get(self):
|
||||
begin = bool(self.get_argument("begin", False))
|
||||
entries = _list_dashboard_entries()
|
||||
|
||||
self.render(
|
||||
get_template_path("index"),
|
||||
entries=entries,
|
||||
begin=begin,
|
||||
**template_args(),
|
||||
login_enabled=settings.using_auth,
|
||||
@@ -495,6 +551,8 @@ def _ping_func(filename, address):
|
||||
|
||||
class MDNSStatusThread(threading.Thread):
|
||||
def run(self):
|
||||
global IMPORT_RESULT
|
||||
|
||||
zc = EsphomeZeroconf()
|
||||
|
||||
def on_update(dat):
|
||||
@@ -502,17 +560,22 @@ class MDNSStatusThread(threading.Thread):
|
||||
PING_RESULT[key] = b
|
||||
|
||||
stat = DashboardStatus(zc, on_update)
|
||||
imports = DashboardImportDiscovery(zc)
|
||||
|
||||
stat.start()
|
||||
while not STOP_EVENT.is_set():
|
||||
entries = _list_dashboard_entries()
|
||||
stat.request_query(
|
||||
{entry.filename: f"{entry.name}.local." for entry in entries}
|
||||
)
|
||||
IMPORT_RESULT = imports.import_state
|
||||
|
||||
PING_REQUEST.wait()
|
||||
PING_REQUEST.clear()
|
||||
|
||||
stat.stop()
|
||||
stat.join()
|
||||
imports.cancel()
|
||||
zc.close()
|
||||
|
||||
|
||||
@@ -567,10 +630,6 @@ class PingRequestHandler(BaseHandler):
|
||||
self.write(json.dumps(PING_RESULT))
|
||||
|
||||
|
||||
def is_allowed(configuration):
|
||||
return os.path.sep not in configuration
|
||||
|
||||
|
||||
class InfoRequestHandler(BaseHandler):
|
||||
@authenticated
|
||||
@bind_config
|
||||
@@ -613,20 +672,18 @@ class DeleteRequestHandler(BaseHandler):
|
||||
def post(self, configuration=None):
|
||||
config_file = settings.rel_path(configuration)
|
||||
storage_path = ext_storage_path(settings.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(settings.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(settings.config_dir, name)
|
||||
if build_folder is not None:
|
||||
shutil.rmtree(build_folder, os.path.join(trash_path, name))
|
||||
storage_json = StorageJSON.load(storage_path)
|
||||
if storage_json is not None:
|
||||
# Delete build folder (if exists)
|
||||
name = storage_json.name
|
||||
build_folder = os.path.join(settings.config_dir, name)
|
||||
if build_folder is not None:
|
||||
shutil.rmtree(build_folder, os.path.join(trash_path, name))
|
||||
|
||||
|
||||
class UndoDeleteRequestHandler(BaseHandler):
|
||||
@@ -639,6 +696,7 @@ class UndoDeleteRequestHandler(BaseHandler):
|
||||
|
||||
|
||||
PING_RESULT = {} # type: dict
|
||||
IMPORT_RESULT = {}
|
||||
STOP_EVENT = threading.Event()
|
||||
PING_REQUEST = threading.Event()
|
||||
|
||||
@@ -808,8 +866,10 @@ def make_app(debug=get_bool_env(ENV_DEV)):
|
||||
(f"{rel}ping", PingRequestHandler),
|
||||
(f"{rel}delete", DeleteRequestHandler),
|
||||
(f"{rel}undo-delete", UndoDeleteRequestHandler),
|
||||
(f"{rel}wizard.html", WizardRequestHandler),
|
||||
(f"{rel}wizard", WizardRequestHandler),
|
||||
(f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}),
|
||||
(f"{rel}devices", ListDevicesHandler),
|
||||
(f"{rel}import", ImportRequestHandler),
|
||||
],
|
||||
**app_settings,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user