Skip to content

Commit

Permalink
Fix Restart Frontend returning an error, add ability to set custom la…
Browse files Browse the repository at this point in the history
…yout.html
  • Loading branch information
kizniche committed Sep 3, 2024
1 parent 561b3a1 commit 984f5b1
Show file tree
Hide file tree
Showing 15 changed files with 161 additions and 40 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
40 changes: 40 additions & 0 deletions alembic_db/alembic/versions/9bdb60d2a2cd_add_custom_layout.py
Original file line number Diff line number Diff line change
@@ -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')
7 changes: 5 additions & 2 deletions mycodo/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions mycodo/databases/models/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions mycodo/mycodo_flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions mycodo/mycodo_flask/forms/forms_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down
20 changes: 9 additions & 11 deletions mycodo/mycodo_flask/routes_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
11 changes: 8 additions & 3 deletions mycodo/mycodo_flask/routes_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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')

Expand Down
File renamed without changes.
6 changes: 6 additions & 0 deletions mycodo/mycodo_flask/templates/settings/general.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ <h3 style="text-align: right; padding-bottom: 1.3em;"><a href="https://kizniche.
<textarea class="form-control" id="custom_css" name="custom_css" title="Set custom CSS" rows="4">{{settings.custom_css}}</textarea>
</div>
</div>
<div class="form-group">
{{form_settings_general.custom_layout.label(class_='col-sm-12 control-label checkbox-nopad')}}
<div class="col-sm-12">
<textarea class="form-control" id="custom_layout" name="custom_layout" title="Set custom Layout HTML" rows="4">{{settings.custom_layout}}</textarea>
</div>
</div>
<div class="form-group">
{{form_settings_general.daemon_debug_mode.label(class_='col-sm-12 control-label checkbox-nopad')}}
<div class="col-sm-12">
Expand Down
6 changes: 3 additions & 3 deletions mycodo/mycodo_flask/utils/utils_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
]

Expand All @@ -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")
]

Expand Down
26 changes: 19 additions & 7 deletions mycodo/mycodo_flask/utils/utils_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -363,6 +365,7 @@ def user_del(form):
# Settings modifications
#


def settings_general_mod(form):
"""Modify General settings."""
messages = {
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
48 changes: 48 additions & 0 deletions mycodo/utils/layouts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
#
# layouts.py - Mycodo core utils
#
# Copyright (C) 2015-2020 Kyle T. Gabriel <[email protected]>
#
# 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 <http://www.gnu.org/licenses/>.
#
# 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")
4 changes: 2 additions & 2 deletions mycodo/utils/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
Loading

0 comments on commit 984f5b1

Please sign in to comment.