diff --git a/CHANGELOG.md b/CHANGELOG.md index 459cb3f51..d6be3ed0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This release changes the install directory from ~/Mycodo to /opt/Mycodo. This ne - Fix MCP23017 Pump Output KeyError - Fix displaying Tags on Highcharts Widget - Fix dependencies of Mijia LYWSD03MMC + - Fix Restart Frontend returning an error ### Features @@ -27,6 +28,7 @@ This release changes the install directory from ~/Mycodo to /opt/Mycodo. This ne - Add Output: GP8XXX (8403) 2-Channel DAC (0-10 VDC) ([#1354](https://github.com/kizniche/Mycodo/issues/1354)) - Add Output: XL9535 16-Channel On/Off IO-Expander - Add Widget: Measurement (2 Values) + - Add ability to set custom layout.html - Add API Endpoint: /notes/create to create a Note ([#1357](https://github.com/kizniche/Mycodo/issues/1357)) - Add ability to switch displaying hostname with custom text - Add Step Line Series Type to Graph (Synchronous) Widget diff --git a/alembic_db/alembic/versions/9bdb60d2a2cd_add_custom_layout.py b/alembic_db/alembic/versions/9bdb60d2a2cd_add_custom_layout.py new file mode 100644 index 000000000..8c53fe343 --- /dev/null +++ b/alembic_db/alembic/versions/9bdb60d2a2cd_add_custom_layout.py @@ -0,0 +1,40 @@ +"""Add custom_layout + +Revision ID: 9bdb60d2a2cd +Revises: d6b624da47f4 +Create Date: 2024-09-03 13:44:20.678365 + +""" +import sys +import os + +sys.path.append(os.path.abspath(os.path.join(__file__, "../../../.."))) + +from alembic_db.alembic_post_utils import write_revision_post_alembic + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9bdb60d2a2cd' +down_revision = 'd6b624da47f4' +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table("misc") as batch_op: + batch_op.add_column(sa.Column('custom_layout', sa.Text)) + + op.execute( + ''' + UPDATE misc + SET custom_layout='' + ''' + ) + + +def downgrade(): + with op.batch_alter_table("misc") as batch_op: + batch_op.drop_column('custom_layout') diff --git a/mycodo/config.py b/mycodo/config.py index c889a282c..488fb5679 100644 --- a/mycodo/config.py +++ b/mycodo/config.py @@ -14,7 +14,7 @@ from config_translations import TRANSLATIONS as T MYCODO_VERSION = '8.15.13' -ALEMBIC_VERSION = 'd6b624da47f4' +ALEMBIC_VERSION = '9bdb60d2a2cd' # FORCE UPGRADE MASTER # Set True to enable upgrading to the master branch of the Mycodo repository. @@ -55,8 +55,11 @@ PATH_INPUTS_CUSTOM = os.path.join(PATH_INPUTS, 'custom_inputs') PATH_OUTPUTS_CUSTOM = os.path.join(PATH_OUTPUTS, 'custom_outputs') PATH_WIDGETS_CUSTOM = os.path.join(PATH_WIDGETS, 'custom_widgets') +PATH_TEMPLATE = os.path.join(INSTALL_DIRECTORY, 'mycodo/mycodo_flask/templates') +PATH_TEMPLATE_LAYOUT = os.path.join(PATH_TEMPLATE, 'layout.html') +PATH_TEMPLATE_LAYOUT_DEFAULT = os.path.join(PATH_TEMPLATE, 'layout_default.html') +PATH_TEMPLATE_USER = os.path.join(PATH_TEMPLATE, 'user_templates') PATH_USER_SCRIPTS = os.path.join(INSTALL_DIRECTORY, 'mycodo/user_scripts') -PATH_HTML_USER = os.path.join(INSTALL_DIRECTORY, 'mycodo/mycodo_flask/templates/user_templates') PATH_PYTHON_CODE_USER = os.path.join(INSTALL_DIRECTORY, 'mycodo/user_python_code') PATH_MEASUREMENTS_BACKUP = os.path.join(INSTALL_DIRECTORY, 'mycodo/backup_measurements') PATH_SETTINGS_BACKUP = os.path.join(INSTALL_DIRECTORY, 'mycodo/backup_settings') diff --git a/mycodo/databases/models/misc.py b/mycodo/databases/models/misc.py index 628e44659..109e764d6 100644 --- a/mycodo/databases/models/misc.py +++ b/mycodo/databases/models/misc.py @@ -53,6 +53,7 @@ class Misc(CRUDMixin, db.Model): brand_image = db.Column(db.BLOB, default=b'') brand_image_height = db.Column(db.Integer, default=55) custom_css = db.Column(db.String, default='') + custom_layout = db.Column(db.String, default='') # Measurement database db_name = 'influxdb' # Default diff --git a/mycodo/mycodo_flask/app.py b/mycodo/mycodo_flask/app.py index 776a01154..0b2ef3fac 100755 --- a/mycodo/mycodo_flask/app.py +++ b/mycodo/mycodo_flask/app.py @@ -27,6 +27,7 @@ from mycodo.mycodo_flask.api import api_blueprint, init_api from mycodo.mycodo_flask.extensions import db from mycodo.mycodo_flask.utils.utils_general import get_ip_address +from mycodo.utils.layouts import update_layout from mycodo.utils.widgets import parse_widget_information logger = logging.getLogger(__name__) @@ -78,9 +79,13 @@ def register_extensions(app): if app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://': with session_scope(app.config['SQLALCHEMY_DATABASE_URI']) as new_session: misc = new_session.query(Misc).first() - if misc and misc.force_https: - csp = {'default-src': ['*', '\'unsafe-inline\'', '\'unsafe-eval\'']} - Talisman(app, content_security_policy=csp) + if misc: + # Ensure layout.html is present, by generating it at startup + update_layout(misc.custom_layout) + + if misc.force_https: + csp = {'default-src': ['*', '\'unsafe-inline\'', '\'unsafe-eval\'']} + Talisman(app, content_security_policy=csp) def register_blueprints(app): diff --git a/mycodo/mycodo_flask/forms/forms_settings.py b/mycodo/mycodo_flask/forms/forms_settings.py index c7d8ace29..6d390ef69 100644 --- a/mycodo/mycodo_flask/forms/forms_settings.py +++ b/mycodo/mycodo_flask/forms/forms_settings.py @@ -92,6 +92,7 @@ class SettingsGeneral(FlaskForm): language = StringField(lazy_gettext('Language')) rpyc_timeout = StringField(lazy_gettext('Pyro Timeout')) custom_css = StringField(lazy_gettext('Custom CSS'), widget=TextArea()) + custom_layout = StringField(lazy_gettext('Custom Layout'), widget=TextArea()) brand_display = StringField(lazy_gettext('Brand Display')) title_display = StringField(lazy_gettext('Title Display')) hostname_override = StringField(lazy_gettext('Brand Text')) diff --git a/mycodo/mycodo_flask/routes_dashboard.py b/mycodo/mycodo_flask/routes_dashboard.py index d07709ef6..e01a3b675 100644 --- a/mycodo/mycodo_flask/routes_dashboard.py +++ b/mycodo/mycodo_flask/routes_dashboard.py @@ -9,7 +9,7 @@ from sqlalchemy import and_ from mycodo.config import INSTALL_DIRECTORY -from mycodo.config import PATH_HTML_USER +from mycodo.config import PATH_TEMPLATE_USER from mycodo.databases.models import (PID, Camera, Conditional, Conversion, CustomController, Dashboard, DeviceMeasurements, Input, Measurement, @@ -206,37 +206,37 @@ def page_dashboard(dashboard_id): for each_widget_type in widget_types_on_dashboard: file_html_head = "widget_template_{}_head.html".format(each_widget_type) - path_html_head = os.path.join(PATH_HTML_USER, file_html_head) + path_html_head = os.path.join(PATH_TEMPLATE_USER, file_html_head) if os.path.exists(path_html_head): list_html_files_head[each_widget_type] = file_html_head file_html_title_bar = "widget_template_{}_title_bar.html".format(each_widget_type) - path_html_title_bar = os.path.join(PATH_HTML_USER, file_html_title_bar) + path_html_title_bar = os.path.join(PATH_TEMPLATE_USER, file_html_title_bar) if os.path.exists(path_html_title_bar): list_html_files_title_bar[each_widget_type] = file_html_title_bar file_html_body = "widget_template_{}_body.html".format(each_widget_type) - path_html_body = os.path.join(PATH_HTML_USER, file_html_body) + path_html_body = os.path.join(PATH_TEMPLATE_USER, file_html_body) if os.path.exists(path_html_body): list_html_files_body[each_widget_type] = file_html_body file_html_configure_options = "widget_template_{}_configure_options.html".format(each_widget_type) - path_html_configure_options = os.path.join(PATH_HTML_USER, file_html_configure_options) + path_html_configure_options = os.path.join(PATH_TEMPLATE_USER, file_html_configure_options) if os.path.exists(path_html_configure_options): list_html_files_configure_options[each_widget_type] = file_html_configure_options file_html_js = "widget_template_{}_js.html".format(each_widget_type) - path_html_js = os.path.join(PATH_HTML_USER, file_html_js) + path_html_js = os.path.join(PATH_TEMPLATE_USER, file_html_js) if os.path.exists(path_html_js): list_html_files_js[each_widget_type] = file_html_js file_html_js_ready = "widget_template_{}_js_ready.html".format(each_widget_type) - path_html_js_ready = os.path.join(PATH_HTML_USER, file_html_js_ready) + path_html_js_ready = os.path.join(PATH_TEMPLATE_USER, file_html_js_ready) if os.path.exists(path_html_js_ready): list_html_files_js_ready[each_widget_type] = file_html_js_ready file_html_js_ready_end = "widget_template_{}_js_ready_end.html".format(each_widget_type) - path_html_js_ready_end = os.path.join(PATH_HTML_USER, file_html_js_ready_end) + path_html_js_ready_end = os.path.join(PATH_TEMPLATE_USER, file_html_js_ready_end) if os.path.exists(path_html_js_ready_end): list_html_files_js_ready_end[each_widget_type] = file_html_js_ready_end @@ -330,11 +330,9 @@ def page_dashboard(dashboard_id): @flask_login.login_required def restart_flask_auto_advance_page(dashboard_id=""): """Wait then automatically load next page""" - logger.info("Reloading frontend in 5 seconds") - + logger.info("Reloading frontend in 10 seconds") cmd = f"sleep 10 && {INSTALL_DIRECTORY}/mycodo/scripts/mycodo_wrapper frontend_reload 2>&1" subprocess.Popen(cmd, shell=True) - logger.info("Rendering wait page") return render_template('pages/wait_and_autoload.html', dashboard_id=dashboard_id) diff --git a/mycodo/mycodo_flask/routes_general.py b/mycodo/mycodo_flask/routes_general.py index 6286bc7df..6aedd6029 100644 --- a/mycodo/mycodo_flask/routes_general.py +++ b/mycodo/mycodo_flask/routes_general.py @@ -843,8 +843,13 @@ def computer_command(action): elif action == 'frontend_reload': subprocess.Popen('docker restart mycodo_flask 2>&1', shell=True) else: - cmd = f'{INSTALL_DIRECTORY}/mycodo/scripts/mycodo_wrapper {action} 2>&1' - subprocess.Popen(cmd, shell=True) + if action == 'frontend_reload': + logger.info("Reloading frontend in 10 seconds") + cmd = f"sleep 10 && {INSTALL_DIRECTORY}/mycodo/scripts/mycodo_wrapper frontend_reload 2>&1" + subprocess.Popen(cmd, shell=True) + else: + cmd = f'{INSTALL_DIRECTORY}/mycodo/scripts/mycodo_wrapper {action} 2>&1' + subprocess.Popen(cmd, shell=True) if action == 'restart': flash(gettext("System rebooting in 10 seconds"), "success") @@ -853,7 +858,7 @@ def computer_command(action): elif action == 'daemon_restart': flash(gettext("Command to restart the daemon sent"), "success") elif action == 'frontend_reload': - flash(gettext("Command to reload the frontend sent"), "success") + flash(gettext("Frontend reloading in 10 seconds"), "success") return redirect('/settings') diff --git a/mycodo/mycodo_flask/templates/layout.html b/mycodo/mycodo_flask/templates/layout_default.html similarity index 100% rename from mycodo/mycodo_flask/templates/layout.html rename to mycodo/mycodo_flask/templates/layout_default.html diff --git a/mycodo/mycodo_flask/templates/settings/general.html b/mycodo/mycodo_flask/templates/settings/general.html index 1d445de86..79d066cfa 100644 --- a/mycodo/mycodo_flask/templates/settings/general.html +++ b/mycodo/mycodo_flask/templates/settings/general.html @@ -121,6 +121,12 @@

{{settings.custom_css}} +
+ {{form_settings_general.custom_layout.label(class_='col-sm-12 control-label checkbox-nopad')}} +
+ +
+
{{form_settings_general.daemon_debug_mode.label(class_='col-sm-12 control-label checkbox-nopad')}}
diff --git a/mycodo/mycodo_flask/utils/utils_export.py b/mycodo/mycodo_flask/utils/utils_export.py index bc15a17e8..11bdf6073 100644 --- a/mycodo/mycodo_flask/utils/utils_export.py +++ b/mycodo/mycodo_flask/utils/utils_export.py @@ -16,7 +16,7 @@ from mycodo.config import (ALEMBIC_VERSION, DATABASE_NAME, DOCKER_CONTAINER, IMPORT_LOG_FILE, INSTALL_DIRECTORY, MYCODO_VERSION, PATH_ACTIONS_CUSTOM, PATH_FUNCTIONS_CUSTOM, - PATH_HTML_USER, PATH_INPUTS_CUSTOM, + PATH_TEMPLATE_USER, PATH_INPUTS_CUSTOM, PATH_OUTPUTS_CUSTOM, PATH_PYTHON_CODE_USER, PATH_USER_SCRIPTS, PATH_WIDGETS_CUSTOM, SQL_DATABASE_MYCODO, DATABASE_PATH) @@ -310,7 +310,7 @@ def import_settings(form): PATH_OUTPUTS_CUSTOM, PATH_WIDGETS_CUSTOM, PATH_USER_SCRIPTS, - PATH_HTML_USER, + PATH_TEMPLATE_USER, PATH_PYTHON_CODE_USER ] @@ -336,7 +336,7 @@ def import_settings(form): (PATH_OUTPUTS_CUSTOM, "custom_outputs"), (PATH_WIDGETS_CUSTOM, "custom_widgets"), (PATH_USER_SCRIPTS, "user_scripts"), - (PATH_HTML_USER, "user_html"), + (PATH_TEMPLATE_USER, "user_html"), (PATH_PYTHON_CODE_USER, "user_python_code") ] diff --git a/mycodo/mycodo_flask/utils/utils_settings.py b/mycodo/mycodo_flask/utils/utils_settings.py index 6e6a12f0e..fe1b39225 100644 --- a/mycodo/mycodo_flask/utils/utils_settings.py +++ b/mycodo/mycodo_flask/utils/utils_settings.py @@ -27,6 +27,7 @@ from mycodo.config import PATH_FUNCTIONS_CUSTOM from mycodo.config import PATH_INPUTS_CUSTOM from mycodo.config import PATH_OUTPUTS_CUSTOM +from mycodo.config import PATH_TEMPLATE_USER from mycodo.config import PATH_WIDGETS_CUSTOM from mycodo.config import UPGRADE_INIT_FILE from mycodo.config_devices_units import MEASUREMENTS @@ -65,6 +66,7 @@ from mycodo.utils.database import db_retrieve_table from mycodo.utils.functions import parse_function_information from mycodo.utils.inputs import parse_input_information +from mycodo.utils.layouts import update_layout from mycodo.utils.modules import load_module_from_file from mycodo.utils.outputs import parse_output_information from mycodo.utils.send_data import send_email @@ -363,6 +365,7 @@ def user_del(form): # Settings modifications # + def settings_general_mod(form): """Modify General settings.""" messages = { @@ -382,12 +385,22 @@ def settings_general_mod(form): if not messages["error"]: try: + reload_frontend = False mod_misc = Misc.query.first() - force_https = mod_misc.force_https - mod_misc.force_https = form.force_https.data + if mod_misc.force_https != form.force_https.data: + mod_misc.force_https = form.force_https.data + reload_frontend = True + mod_misc.rpyc_timeout = form.rpyc_timeout.data mod_misc.custom_css = form.custom_css.data + + if mod_misc.custom_layout != form.custom_layout.data: + mod_misc.custom_layout = form.custom_layout.data + assure_path_exists(PATH_TEMPLATE_USER) + update_layout(mod_misc.custom_layout) + reload_frontend = True + mod_misc.brand_display = form.brand_display.data mod_misc.title_display = form.title_display.data mod_misc.hostname_override = form.hostname_override.data @@ -452,11 +465,10 @@ def settings_general_mod(form): action=TRANSLATIONS['modify']['title'], controller=gettext("General Settings"))) - if force_https != form.force_https.data: - # Force HTTPS option changed. - # Reload web server with new settings. - cmd = '{path}/mycodo/scripts/mycodo_wrapper frontend_reload 2>&1'.format( - path=INSTALL_DIRECTORY) + if reload_frontend: + # Reload web server + logger.info("Reloading frontend in 10 seconds") + cmd = f"sleep 10 && {INSTALL_DIRECTORY}/mycodo/scripts/mycodo_wrapper frontend_reload 2>&1" subprocess.Popen(cmd, shell=True) except Exception as except_msg: diff --git a/mycodo/utils/layouts.py b/mycodo/utils/layouts.py new file mode 100644 index 000000000..207f8b497 --- /dev/null +++ b/mycodo/utils/layouts.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# layouts.py - Mycodo core utils +# +# Copyright (C) 2015-2020 Kyle T. Gabriel +# +# This file is part of Mycodo +# +# Mycodo is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Mycodo is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Mycodo. If not, see . +# +# Contact at kylegabriel.com + +import logging +import os +import shutil + +from mycodo.config import PATH_TEMPLATE_LAYOUT +from mycodo.config import PATH_TEMPLATE_LAYOUT_DEFAULT + +logger = logging.getLogger("mycodo.utils.layouts") + + +def update_layout(custom_layout): + try: + if custom_layout: + # Use custom layout + with open(PATH_TEMPLATE_LAYOUT, "w") as template: + template.write(custom_layout) + else: + # Use default layout + if (os.path.exists(PATH_TEMPLATE_LAYOUT) and + not os.path.samefile(PATH_TEMPLATE_LAYOUT, PATH_TEMPLATE_LAYOUT_DEFAULT)): + # Delete current layout if it's different from the default + os.remove(PATH_TEMPLATE_LAYOUT) + shutil.copy(PATH_TEMPLATE_LAYOUT_DEFAULT, PATH_TEMPLATE_LAYOUT) + except: + logger.exception("Generating layout") diff --git a/mycodo/utils/tools.py b/mycodo/utils/tools.py index ab23d57f1..049837ad0 100644 --- a/mycodo/utils/tools.py +++ b/mycodo/utils/tools.py @@ -12,7 +12,7 @@ from dateutil import relativedelta from mycodo.config import (INSTALL_DIRECTORY, PATH_ACTIONS_CUSTOM, - PATH_FUNCTIONS_CUSTOM, PATH_HTML_USER, + PATH_FUNCTIONS_CUSTOM, PATH_TEMPLATE_USER, PATH_INPUTS_CUSTOM, PATH_OUTPUTS_CUSTOM, PATH_PYTHON_CODE_USER, PATH_USER_SCRIPTS, PATH_WIDGETS_CUSTOM, SQL_DATABASE_MYCODO, @@ -95,7 +95,7 @@ def create_settings_export(save_path=None): (PATH_OUTPUTS_CUSTOM, "custom_outputs"), (PATH_WIDGETS_CUSTOM, "custom_widgets"), (PATH_USER_SCRIPTS, "user_scripts"), - (PATH_HTML_USER, "user_html"), + (PATH_TEMPLATE_USER, "user_html"), (PATH_PYTHON_CODE_USER, "user_python_code") ] for each_backup in export_directories: diff --git a/mycodo/utils/widget_generate_html.py b/mycodo/utils/widget_generate_html.py index 2f999c6c3..069cd97e9 100644 --- a/mycodo/utils/widget_generate_html.py +++ b/mycodo/utils/widget_generate_html.py @@ -6,7 +6,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.realpath(__file__), '../../..'))) -from mycodo.config import PATH_HTML_USER +from mycodo.config import PATH_TEMPLATE_USER from mycodo.utils.widgets import parse_widget_information logger = logging.getLogger("mycodo.utils.widget_generate_html") @@ -31,12 +31,12 @@ def assure_path_exists(path): def generate_widget_html(): """Generate all HTML files for all widgets.""" dict_widgets = parse_widget_information() - assure_path_exists(PATH_HTML_USER) + assure_path_exists(PATH_TEMPLATE_USER) for widget_name in dict_widgets: try: filename_head = f"widget_template_{widget_name}_head.html" - path_head = os.path.join(PATH_HTML_USER, filename_head) + path_head = os.path.join(PATH_TEMPLATE_USER, filename_head) with open(path_head, 'w') as fw: if 'widget_dashboard_head' in dict_widgets[widget_name]: html_head = dict_widgets[widget_name]['widget_dashboard_head'] @@ -47,7 +47,7 @@ def generate_widget_html(): set_user_grp(path_head, 'mycodo', 'mycodo') filename_title_bar = f"widget_template_{widget_name}_title_bar.html" - path_title_bar = os.path.join(PATH_HTML_USER, filename_title_bar) + path_title_bar = os.path.join(PATH_TEMPLATE_USER, filename_title_bar) with open(path_title_bar, 'w') as fw: if 'widget_dashboard_title_bar' in dict_widgets[widget_name]: html_title_bar = dict_widgets[widget_name]['widget_dashboard_title_bar'] @@ -58,7 +58,7 @@ def generate_widget_html(): set_user_grp(path_title_bar, 'mycodo', 'mycodo') filename_body = f"widget_template_{widget_name}_body.html" - path_body = os.path.join(PATH_HTML_USER, filename_body) + path_body = os.path.join(PATH_TEMPLATE_USER, filename_body) with open(path_body, 'w') as fw: if 'widget_dashboard_body' in dict_widgets[widget_name]: html_body = dict_widgets[widget_name]['widget_dashboard_body'] @@ -69,7 +69,7 @@ def generate_widget_html(): set_user_grp(path_body, 'mycodo', 'mycodo') filename_configure_options = f"widget_template_{widget_name}_configure_options.html" - path_configure_options = os.path.join(PATH_HTML_USER, filename_configure_options) + path_configure_options = os.path.join(PATH_TEMPLATE_USER, filename_configure_options) with open(path_configure_options, 'w') as fw: if 'widget_dashboard_configure_options' in dict_widgets[widget_name]: html_configure_options = dict_widgets[widget_name]['widget_dashboard_configure_options'] @@ -80,7 +80,7 @@ def generate_widget_html(): set_user_grp(path_configure_options, 'mycodo', 'mycodo') filename_js = f"widget_template_{widget_name}_js.html" - path_js = os.path.join(PATH_HTML_USER, filename_js) + path_js = os.path.join(PATH_TEMPLATE_USER, filename_js) with open(path_js, 'w') as fw: if 'widget_dashboard_js' in dict_widgets[widget_name]: html_js = dict_widgets[widget_name]['widget_dashboard_js'] @@ -91,7 +91,7 @@ def generate_widget_html(): set_user_grp(path_js, 'mycodo', 'mycodo') filename_js_ready = f"widget_template_{widget_name}_js_ready.html" - path_js_ready = os.path.join(PATH_HTML_USER, filename_js_ready) + path_js_ready = os.path.join(PATH_TEMPLATE_USER, filename_js_ready) with open(path_js_ready, 'w') as fw: if 'widget_dashboard_js_ready' in dict_widgets[widget_name]: html_js_ready = dict_widgets[widget_name]['widget_dashboard_js_ready'] @@ -102,7 +102,7 @@ def generate_widget_html(): set_user_grp(path_js_ready, 'mycodo', 'mycodo') filename_js_ready_end = f"widget_template_{widget_name}_js_ready_end.html" - path_js_ready_end = os.path.join(PATH_HTML_USER, filename_js_ready_end) + path_js_ready_end = os.path.join(PATH_TEMPLATE_USER, filename_js_ready_end) with open(path_js_ready_end, 'w') as fw: if 'widget_dashboard_js_ready_end' in dict_widgets[widget_name]: html_js_ready_end = dict_widgets[widget_name]['widget_dashboard_js_ready_end']