diff --git a/config_readme.md b/config_readme.md index a6e98dc..38325e1 100644 --- a/config_readme.md +++ b/config_readme.md @@ -1,11 +1,7 @@ #### Config.ini Readme ##### Settings -This is the configuration entry for DashMachine's settings. DashMachine will not work if -this is missing. As for all config entries, [Settings] can only appear once in the config. -If you change the config.ini file, you either have to restart the container -(or python script) or click the ‘save’ button in the config section of settings for the -config to be applied. +This is the configuration entry for DashMachine's settings. DashMachine will not work if this is missing. As for all config entries, [Settings] can only appear once in the config. If you change the config.ini file, you either have to restart the container (or python script) or click the ‘save’ button in the config section of settings for the config to be applied. ```ini [Settings] theme = light @@ -14,7 +10,9 @@ background = None roles = admin,user,public_user home_access_groups = admin_only settings_access_groups = admin_only +home_view_mode = grid custom_app_title = DashMachine +sidebar_default = open ``` | Variable | Required | Description | Options | @@ -26,36 +24,32 @@ custom_app_title = DashMachine | roles | No | User roles for access groups. | comma separated string, if not defined, this is set to 'admin,user,public_user'. Note: admin, user, public_user roles are required and will be added automatically if omitted. | | home_access_groups | No | Define which access groups can access the /home page | Groups defined in your config. If not defined, default is admin_only | | settings_access_groups | No | Define which access groups can access the /settings page | Groups defined in your config. If not defined, default is admin_only | +| home_view_mode | No | Choose the default for view mode on /home | grid, list | | custom_app_title | No | Change the title of the app for browser tabs | string | +| sidebar_default | No | Select the default state for the sidebar | open, closed, no_sidebar | ##### Users -Each user requires a config entry, and there must be at least one user in the config -(otherwise the default user is added). Each user has a username, a role for configuring -access groups, and a password. By default there is one user, named 'admin', with role -'admin' and password 'admin'. To change this user's name, password or role, -just modify the config entry's variables and press save. To add a new user, add another -user config entry UNDER all existing user config entries. A user with role 'admin' must -appear first in the config. Do not change the order of users in the config -once they have been defined, otherwise their passwords will not match the next time the -config is applied. When users are removed from the config, they are deleted and their -cached password is also deleted when the config is applied. +Each user requires a config entry, and there must be at least one user in the config (otherwise the default user is added). Each user has a username, a role for configuring access groups, and a password. By default there is one user, named 'admin', with role 'admin' and password 'admin'. To change this user's name, password or role, just modify the config entry's variables and press save. To add a new user, add another user config entry UNDER all existing user config entries. A user with role 'admin' must appear first in the config. Do not change the order of users in the config once they have been defined, otherwise their passwords will not match the next time the config is applied. When users are removed from the config, they are deleted and their cached password is also deleted when the config is applied. ```ini -[Username] +[admin] role = admin password = admin confirm_password = admin ``` -| Variable | Required | Description | Options | -|------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| -| [Username] | Yes | The user's name for logging in | [Username] | -| role | Yes | The user's role. This is used for access groups and controlling who can view /home and /settings. There must be at least one 'admin' user, and it must be defined first in the config. Otherwise, the first user will be set to admin. | string | -| password | No | Add a password to this variable to change the password for this user. The password will be hashed, cached and removed from the config. When adding a new user, specify the password, otherwise 'admin' will be used. | string | -| confirm_password | No | When adding a new user or changing an existing user's password you must confirm the password in this variable | string | +| Variable | Required | Description | Options | +|------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------| +| [Username] | Yes | The user's name for logging in | [Username] | +| role | Yes | The user's role. This is used for access groups and controlling who can view /home and /settings. There must be at least one 'admin' user, and it must be defined first in the config. Otherwise, the first user will be set to admin. | string | +| password | No | Add a password to this variable to change the password for this user. The password will be hashed, cached and removed from the config. When adding a new user, specify the password, otherwise 'admin' will be used. | string | +| confirm_password | No | When adding a new user or changing an existing user's password you must confirm the password in this variable | string | +| theme | No | Override the theme from Settings for this user | same as Settings | +| accent | No | Override the accent from Settings for this user | same as Settings | +| sidebar_default | No | Override the sidebar_default from Settings for this user | same as Settings | +| home_view_mode | No | Override the home_view_mode from Settings for this user | same as Settings | ##### Apps -These entries are the cards that you see one the home page, as well as the sidenav. Entries -must be unique. They are displayed in the order that they appear in config.ini +These entries are the cards that you see one the home page, as well as the sidenav. Entries must be unique. They are displayed in the order that they appear in config.ini ```ini [App Name] prefix = https:// @@ -83,10 +77,7 @@ groups = admin_only | groups | No | Optionally the access groups that can see this app. | comma separated string | ##### Access Groups -You can create access groups to control what user roles can access parts of the ui. Access groups are just a collection of roles, and each user has an attribute 'role'. Each -application can have an access group, if the user's role is not in the group, the app will be hidden. -Also, in the settings entry you can specify `home_access_groups` and `settings_access_groups` to control -which groups can access /home and /settings +You can create access groups to control what user roles can access parts of the ui. Access groups are just a collection of roles, and each user has an attribute 'role'. Each application can have an access group, if the user's role is not in the group, the app will be hidden. Also, in the settings entry you can specify `home_access_groups` and `settings_access_groups` to control which groups can access /home and /settings ```ini [admin_only] roles = admin @@ -132,12 +123,7 @@ roles = admin >Dashmachine will automatically add `admin,user,public_user`, so really you would have 4 roles: `my_people,admin,user,public_user`. Also, the `admin_only` group is required and added by default if omitted. #### Data Source Platforms -DashMachine includes several different 'platforms' for displaying data on your dash applications. -Platforms are essentially plugins. All data source config entries require the `platform` variable, -which tells DashMachine which platform file in the platform folder to load. **Note:** you are able to -load your own platform files by placing them in the platform folder and referencing them in the config. -However currently they will be deleted if you update the application, if you would like to make them -permanent, submit a pull request for it to be added by default! +DashMachine includes several different 'platforms' for displaying data on your dash applications. Platforms are essentially plugins. All data source config entries require the `platform` variable, which tells DashMachine which platform file in the platform folder to load. **Note:** you are able to load your own platform files by placing them in the platform folder and referencing them in the config. However currently they will be deleted if you update the application, if you would like to make them permanent, submit a pull request for it to be added by default! > To add a data source to your app, add a data source config entry from one of the samples below **above** the application entry in config.ini, then add the following to your app config entry: diff --git a/dashmachine/auth_cache/1 b/dashmachine/auth_cache/1 deleted file mode 100644 index f94d91b..0000000 --- a/dashmachine/auth_cache/1 +++ /dev/null @@ -1 +0,0 @@ -$2b$12$fB4kz89m4U7Dc9vpXLhDQ.47iNO4.XuIwqdD63lp8tMWIDgMN4y.a \ No newline at end of file diff --git a/dashmachine/main/read_config.py b/dashmachine/main/read_config.py index c71a5ad..bd81859 100644 --- a/dashmachine/main/read_config.py +++ b/dashmachine/main/read_config.py @@ -74,6 +74,8 @@ def read_config(): "custom_app_title", "DashMachine" ) + settings.sidebar_default = config["Settings"].get("sidebar_default", "open") + db.session.add(settings) db.session.commit() @@ -82,6 +84,10 @@ def read_config(): user = User() user.username = section user.role = config[section]["role"] + user.sidebar_default = config[section].get("sidebar_default", None) + user.home_view_mode = config[section].get("home_view_mode", "grid") + user.theme = config[section].get("theme", None) + user.accent = config[section].get("accent", None) user.password = "" if not User.query.filter_by(role="admin").first() and user.role != "admin": print( diff --git a/dashmachine/main/routes.py b/dashmachine/main/routes.py index 9196844..fa2aaa6 100755 --- a/dashmachine/main/routes.py +++ b/dashmachine/main/routes.py @@ -5,13 +5,13 @@ from htmlmin.main import minify from configparser import ConfigParser from flask import render_template, url_for, redirect, request, Blueprint, jsonify from flask_login import current_user -from dashmachine.main.models import Files, Apps, DataSources, Tags -from dashmachine.main.forms import TagsForm +from dashmachine.main.models import Files, Apps, DataSources from dashmachine.main.utils import ( - public_route, check_groups, get_data_source, + mark_update_message_read, ) +from dashmachine.user_system.models import User from dashmachine.settings_system.models import Settings from dashmachine.paths import cache_folder, user_data_folder from dashmachine import app, db @@ -35,28 +35,9 @@ def response_minify(response): return response -# blocks access to all pages (except public routes) unless the user is -# signed in. -@main.before_app_request -def check_valid_login(): - - if any( - [ - request.endpoint.startswith("static"), - current_user.is_authenticated, - getattr(app.view_functions[request.endpoint], "is_public", False), - ] - ): - return - - else: - return redirect(url_for("user_system.login")) - - # ------------------------------------------------------------------------------ # /home # ------------------------------------------------------------------------------ -@public_route @main.route("/") @main.route("/home", methods=["GET"]) def home(): @@ -66,7 +47,6 @@ def home(): return render_template("main/home.html") -@public_route @main.route("/app_view?", methods=["GET"]) def app_view(app_id): settings = Settings.query.first() @@ -85,20 +65,25 @@ def load_data_source(): return data -@public_route -@main.route("/change_home_view_mode?", methods=["GET"]) -def change_home_view_mode(mode): +@main.route("/change_home_view_mode?%", methods=["GET"]) +def change_home_view_mode(mode, user_id): + user = User.query.filter_by(id=user_id).first() config = ConfigParser() config.read(os.path.join(user_data_folder, "config.ini")) - config.set("Settings", "home_view_mode", mode) + config.set(user.username, "home_view_mode", mode) config.write(open(os.path.join(user_data_folder, "config.ini"), "w")) - settings = Settings.query.first() - settings.home_view_mode = mode - db.session.merge(settings) + user.home_view_mode = mode + db.session.merge(user) db.session.commit() return redirect(url_for("main.home")) +@main.route("/update_message_read", methods=["GET"]) +def update_message_read(): + mark_update_message_read() + return "ok" + + # ------------------------------------------------------------------------------ # TCDROP routes # ------------------------------------------------------------------------------ diff --git a/dashmachine/main/utils.py b/dashmachine/main/utils.py index f3b27a7..b0fa6be 100755 --- a/dashmachine/main/utils.py +++ b/dashmachine/main/utils.py @@ -2,9 +2,16 @@ import os import importlib from shutil import copyfile from PIL import Image -from dashmachine.paths import dashmachine_folder, images_folder +from markdown2 import markdown +from dashmachine.paths import ( + dashmachine_folder, + images_folder, + root_folder, + user_data_folder, +) from dashmachine.main.models import Groups from dashmachine.main.read_config import read_config +from dashmachine.version import version as dashmachine_version from dashmachine import db @@ -94,3 +101,35 @@ def resize_template_app_images(): image = Image.open(fp) image.thumbnail((64, 64)) image.save(fp) + + +def get_update_message_html(): + try: + with open(os.path.join(user_data_folder, ".has_read_update"), "r") as has_read: + has_read_version = has_read.read() + except FileNotFoundError: + has_read_version = None + if not has_read_version or has_read_version.strip() != dashmachine_version: + with open( + os.path.join(root_folder, "update_message.md"), "r" + ) as update_message: + md = update_message.read() + + config_html = markdown( + md, + extras=[ + "tables", + "fenced-code-blocks", + "break-on-newline", + "header-ids", + "code-friendly", + ], + ) + return config_html + else: + return "" + + +def mark_update_message_read(): + with open(os.path.join(user_data_folder, ".has_read_update"), "w") as has_read: + has_read.write(dashmachine_version) diff --git a/dashmachine/paths.py b/dashmachine/paths.py index dbd2d7d..7f624f1 100755 --- a/dashmachine/paths.py +++ b/dashmachine/paths.py @@ -13,14 +13,17 @@ root_folder = get_root_folder() dashmachine_folder = os.path.join(root_folder, "dashmachine") -auth_cache = os.path.join(dashmachine_folder, "auth_cache") - template_apps_folder = os.path.join(root_folder, "template_apps") platform_folder = os.path.join(dashmachine_folder, "platform") user_data_folder = os.path.join(dashmachine_folder, "user_data") +auth_cache = os.path.join(user_data_folder, "auth_cache") + +if not os.path.isdir(auth_cache): + os.mkdir(auth_cache) + static_folder = os.path.join(dashmachine_folder, "static") images_folder = os.path.join(static_folder, "images") diff --git a/dashmachine/settings_system/models.py b/dashmachine/settings_system/models.py index ffd4acc..faac7aa 100644 --- a/dashmachine/settings_system/models.py +++ b/dashmachine/settings_system/models.py @@ -11,3 +11,4 @@ class Settings(db.Model): settings_access_groups = db.Column(db.String()) home_view_mode = db.Column(db.String()) custom_app_title = db.Column(db.String()) + sidebar_default = db.Column(db.String()) diff --git a/dashmachine/sources.py b/dashmachine/sources.py index 6055ccd..0962dfc 100644 --- a/dashmachine/sources.py +++ b/dashmachine/sources.py @@ -4,7 +4,7 @@ from jsmin import jsmin from flask_login import current_user from dashmachine import app from dashmachine.main.models import Apps -from dashmachine.main.utils import check_groups +from dashmachine.main.utils import check_groups, get_update_message_html from dashmachine.main.forms import TagsForm from dashmachine.settings_system.models import Settings from dashmachine.paths import static_folder, backgrounds_images_folder @@ -100,6 +100,7 @@ def context_processor(): f"static/images/backgrounds/" f"{random.choice(os.listdir(backgrounds_images_folder))}" ) + update_message = get_update_message_html() return dict( test_key="test", process_js_sources=process_js_sources, @@ -107,4 +108,5 @@ def context_processor(): apps=apps, settings=settings, tags_form=tags_form, + update_message=update_message, ) diff --git a/dashmachine/static/css/settings_system/settings.css b/dashmachine/static/css/settings_system/settings.css index eb92ad7..9dfd525 100644 --- a/dashmachine/static/css/settings_system/settings.css +++ b/dashmachine/static/css/settings_system/settings.css @@ -47,6 +47,7 @@ -ms-user-select: all; user-select: all; cursor: text; + white-space: pre-wrap; } #config-readme th { color: var(--theme-primary); diff --git a/dashmachine/static/js/global/dashmachine.js b/dashmachine/static/js/global/dashmachine.js index 9ebbf19..b95d585 100644 --- a/dashmachine/static/js/global/dashmachine.js +++ b/dashmachine/static/js/global/dashmachine.js @@ -67,6 +67,13 @@ function hide_sidenav() { localStorage.setItem('sidenav_hidden', 'true'); } +function no_sidebar() { + $("#main-sidenav").remove(); + $("#main.main-full").css('padding-left', 0); + $("#no-sidenav").removeClass('hide'); + localStorage.setItem('sidenav_hidden', 'no_sidebar'); +} + function show_sidenav(){ $("#main-sidenav").removeClass('hide'); $("#main.main-full").css('padding-left', 64); @@ -74,26 +81,80 @@ function show_sidenav(){ localStorage.setItem('sidenav_hidden', null); } -function apply_settings(settings_theme, settings_accent){ - localStorage.setItem('mode', settings_theme); - document.documentElement.setAttribute('data-theme', settings_theme); - localStorage.setItem('accent', settings_accent); - document.documentElement.setAttribute('data-accent', settings_accent); +function apply_settings(settings){ + // theme + if (settings['user_theme'] != "None" && settings['user_theme'].length > 1) { + console.log(settings['user_theme'].length) + localStorage.setItem('mode', settings['user_theme']); + document.documentElement.setAttribute('data-theme', settings['user_theme']); + } else { + localStorage.setItem('mode', settings['settings_theme']); + document.documentElement.setAttribute('data-theme', settings['settings_theme']); + } + // accent + if (settings['user_accent'] != "None" && settings['user_accent'].length > 1) { + localStorage.setItem('accent', settings['user_accent']); + document.documentElement.setAttribute('data-accent', settings['user_accent']); + } else { + localStorage.setItem('accent', settings['settings_accent']); + document.documentElement.setAttribute('data-accent', settings['settings_accent']); + } + if (settings['settings_sidebar_default'] == "closed"){ + localStorage.setItem('sidenav_hidden', 'true'); + } else if (settings['settings_sidebar_default'] == "open"){ + localStorage.setItem('sidenav_hidden', 'false'); + } else if (settings['settings_sidebar_default'] == "no_sidebar"){ + localStorage.setItem('sidenav_hidden', 'no_sidebar'); + } + if (settings['user_sidebar_default'] == "closed"){ + localStorage.setItem('sidenav_hidden', 'true'); + } else if (settings['user_sidebar_default'] == "open"){ + localStorage.setItem('sidenav_hidden', 'false'); + } else if (settings['user_sidebar_default'] == "no_sidebar"){ + localStorage.setItem('sidenav_hidden', 'no_sidebar'); + } + if (localStorage.getItem('sidenav_hidden') === 'true'){ + hide_sidenav(); + } else if (localStorage.getItem('sidenav_hidden') === 'no_sidebar'){ + no_sidebar(); + } else if (settings['user_name'].length < 1) { + no_sidebar(); + } } //-------------------------------------------------------------------------------------- // Document ready function //-------------------------------------------------------------------------------------- $(document).ready(function () { - apply_settings($("#settings-theme").val(), $("#settings-accent").val()); "use strict"; + apply_settings({ + settings_theme: $("#settings-theme").val(), + settings_accent: $("#settings-accent").val(), + settings_sidebar_default: $("#settings-sidebar_default").val(), + user_name: $("#user-name").val(), + user_theme: $("#user-theme").val(), + user_accent: $("#user-accent").val(), + user_sidebar_default: $("#user-sidebar_default").val(), + }); // INITS init_select(); - if (localStorage.getItem('sidenav_hidden') === 'true'){ - hide_sidenav(); + $("#update-message-modal").modal({ + dismissible: false + }); + if ($("#update-message-content").text().length > 1){ + $("#update-message-modal").modal('open'); } + $("#update-message-read-btn").on('click', function(e) { + $.ajax({ + url: $(this).attr('data-url'), + type: 'GET', + success: function(data){ + $("#update-message-modal").modal('close'); + } + }); + }); $("#hide-sidenav").on('click', function(e) { hide_sidenav(); diff --git a/dashmachine/templates/main/base.html b/dashmachine/templates/main/base.html index 3ae5f27..75fd999 100644 --- a/dashmachine/templates/main/base.html +++ b/dashmachine/templates/main/base.html @@ -1,3 +1,4 @@ +{% from 'global_macros.html' import button %} @@ -47,18 +48,44 @@ data-menu="vertical-dark-menu" data-col="2-columns"> + {# System settings from database #} + + + {# User settings from database #} + + + + + {% block header %}{% endblock header %} {% block sidenav %}{% endblock sidenav %} - {% block content %} - {% endblock content %} + {% block content %}{% endblock content %} {% block footer %}{% endblock footer %} @@ -86,13 +113,6 @@ - - - diff --git a/dashmachine/templates/main/home.html b/dashmachine/templates/main/home.html index a17121d..d1022a3 100644 --- a/dashmachine/templates/main/home.html +++ b/dashmachine/templates/main/home.html @@ -28,13 +28,13 @@ search - {% if current_user.role == "admin" %} - {% if settings.home_view_mode == "list" %} - + {% if current_user.is_authenticated %} + {% if current_user.home_view_mode == "list" %} + apps {% else %} - + list {% endif %} @@ -51,7 +51,7 @@ {% if apps %} {# If tags are enabled, render the apps like this #} {% if tags_form.tags.choices|count > 1 %} - {% if settings.home_view_mode == "list" %} + {% if settings.home_view_mode == "list" or current_user.home_view_mode == "list" %}
@@ -96,8 +96,7 @@ {% else %} {# otherwise, render the apps like this #} - {% if settings.home_view_mode == "list" %} - + {% if settings.home_view_mode == "list" or current_user.home_view_mode == "list" %}
{% for app in apps %} diff --git a/dashmachine/templates/main/layout.html b/dashmachine/templates/main/layout.html index b8f7656..d793ac7 100644 --- a/dashmachine/templates/main/layout.html +++ b/dashmachine/templates/main/layout.html @@ -31,13 +31,13 @@ Logout {% else %} -
  • +
  • account_circle Login
  • {% endif %} -
  • +
  • menu_open Hide Sidenav
  • @@ -62,6 +62,27 @@ {% endblock sidenav%} {% block footer %} +
    +
    + + {% if current_user.is_authenticated %} + + exit_to_app + + {% else %} + + account_circle + + {% endif %} + + settings + + + dashboard + + +
    +