diff --git a/config_readme.md b/config_readme.md index 38325e1..e6f814c 100644 --- a/config_readme.md +++ b/config_readme.md @@ -1,4 +1,12 @@ -#### Config.ini Readme +- [Main Settings](#main-settings) + * [Settings](#settings) + * [Users](#users) + * [Access Groups](#access-groups) +- [Cards](#cards) + * [Apps](#apps) +- [Data Source Platforms](#data-source-platforms) + +#### Main Settings ##### 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. @@ -10,7 +18,6 @@ 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 ``` @@ -24,7 +31,6 @@ sidebar_default = open | 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 | @@ -46,35 +52,6 @@ confirm_password = admin | 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 -```ini -[App Name] -prefix = https:// -url = your-website.com -icon = static/images/apps/default.png -sidebar_icon = static/images/apps/default.png -description = Example description -open_in = iframe -data_sources = None -tags = Example Tag -groups = admin_only -``` - -| Variable | Required | Description | Options | -|--------------|----------|-------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| -| [App Name] | Yes | The name of your app. | [App Name] | -| prefix | Yes | The prefix for the app's url. | web prefix, e.g. http:// or https:// | -| url | Yes | The url for your app. | web url, e.g. myapp.com | -| open_in | Yes | open the app in the current tab, an iframe or a new tab | iframe, new_tab, this_tab | -| icon | Yes | Icon for the dashboard. | /static/images/icons/yourpicture.png, external link to image | -| sidebar_icon | No | Icon for the sidenav. | /static/images/icons/yourpicture.png, external link to image | -| description | No | A short description for the app. | string | -| data_sources | No | Data sources to be included on the app's card.*Note: you must have a data source set up in the config above this application entry. | comma separated string | -| tags | No | Optionally specify tags for organization on /home | comma separated string | -| 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 @@ -122,6 +99,36 @@ 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. +#### Cards + +##### 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 +```ini +[App Name] +prefix = https:// +url = your-website.com +icon = static/images/apps/default.png +sidebar_icon = static/images/apps/default.png +description = Example description +open_in = iframe +data_sources = None +tags = Example Tag +groups = admin_only +``` + +| Variable | Required | Description | Options | +|--------------|----------|-------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| [App Name] | Yes | The name of your app. | [App Name] | +| prefix | Yes | The prefix for the app's url. | web prefix, e.g. http:// or https:// | +| url | Yes | The url for your app. | web url, e.g. myapp.com | +| open_in | Yes | open the app in the current tab, an iframe or a new tab | iframe, new_tab, this_tab | +| icon | Yes | Icon for the dashboard. | /static/images/icons/yourpicture.png, external link to image | +| sidebar_icon | No | Icon for the sidenav. | /static/images/icons/yourpicture.png, external link to image | +| description | No | A short description for the app. | string | +| data_sources | No | Data sources to be included on the app's card.*Note: you must have a data source set up in the config above this application entry. | comma separated string | +| tags | No | Optionally specify tags for organization on /home | comma separated string | +| groups | No | Optionally the access groups that can see this app. | comma separated string | + #### 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! diff --git a/dashmachine/main/models.py b/dashmachine/main/models.py index c5ea5af..013045a 100644 --- a/dashmachine/main/models.py +++ b/dashmachine/main/models.py @@ -28,6 +28,8 @@ class Apps(db.Model): data_template = db.Column(db.String()) groups = db.Column(db.String()) tags = db.Column(db.String()) + type = db.Column(db.String()) + urls = db.Column(db.String()) class DataSources(db.Model): @@ -58,3 +60,5 @@ class Groups(db.Model): class Tags(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String()) + icon = db.Column(db.String()) + sort_pos = db.Column(db.Integer) diff --git a/dashmachine/main/read_config.py b/dashmachine/main/read_config.py index bd81859..2a019a4 100644 --- a/dashmachine/main/read_config.py +++ b/dashmachine/main/read_config.py @@ -1,4 +1,5 @@ import os +import json from configparser import ConfigParser from dashmachine.main.models import Apps, Groups, DataSources, DataSourcesArgs, Tags from dashmachine.user_system.models import User @@ -68,7 +69,6 @@ def read_config(): settings.settings_access_groups = config["Settings"].get( "settings_access_groups", "admin_only" ) - settings.home_view_mode = config["Settings"].get("home_view_mode", "grid") settings.custom_app_title = config["Settings"].get( "custom_app_title", "DashMachine" @@ -76,6 +76,8 @@ def read_config(): settings.sidebar_default = config["Settings"].get("sidebar_default", "open") + settings.tags_expanded = config["Settings"].get("tags_expanded", "True") + db.session.add(settings) db.session.commit() @@ -85,9 +87,9 @@ def read_config(): 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.tags_expanded = config[section].get("tags_expanded", None) user.password = "" if not User.query.filter_by(role="admin").first() and user.role != "admin": print( @@ -146,35 +148,25 @@ def read_config(): # App creation app = Apps() app.name = section - if "prefix" in config[section]: - app.prefix = config[section]["prefix"] - else: + app.type = config[section].get("type", "app") + + app.prefix = config[section].get("prefix", None) + if app.type == "app" and not app.prefix: return {"msg": f"Invalid Config: {section} does not contain prefix."} - if "url" in config[section]: - app.url = config[section]["url"] - else: + app.url = config[section].get("url", None) + if app.type == "app" and not app.url: return {"msg": f"Invalid Config: {section} does not contain url."} - if "icon" in config[section]: - app.icon = config[section]["icon"] - else: - app.icon = None + app.icon = config[section].get("icon", None) - if "sidebar_icon" in config[section]: - app.sidebar_icon = config[section]["sidebar_icon"] - else: - app.sidebar_icon = app.icon + app.sidebar_icon = config[section].get("sidebar_icon", None) - if "description" in config[section]: - app.description = config[section]["description"] - else: - app.description = None + app.description = config[section].get("description", None) - if "open_in" in config[section]: - app.open_in = config[section]["open_in"] - else: - app.open_in = "this_tab" + app.open_in = config[section].get("open_in", "this_tab") + + app.urls = config[section].get("urls", None) if "groups" in config[section]: for group_name in config[section]["groups"].split(","): @@ -186,14 +178,18 @@ def read_config(): else: app.groups = None + # Tags creation if "tags" in config[section]: - app.tags = config[section]["tags"].title() + app.tags = config[section]["tags"] for tag in app.tags.split(","): - tag = tag.strip().title() + tag = tag.strip() if not Tags.query.filter_by(name=tag).first(): tag_db = Tags(name=tag) db.session.add(tag_db) db.session.commit() + tag_db.sort_pos = tag_db.id + db.session.merge(tag_db) + db.session.commit() else: if Tags.query.first(): app.tags = "Untagged" @@ -227,6 +223,23 @@ def read_config(): db.session.add(group) db.session.commit() + tags_settings = config["Settings"].get("tags", None) + if tags_settings: + tags_settings = tags_settings.replace("},{", "}%,%{").split("%,%") + + for tag_setting in tags_settings: + tag_json = json.loads(tag_setting) + tag = Tags.query.filter_by(name=tag_json.get("name", None)).first() + if tag: + icon = tag_json.get("icon", None) + if icon: + tag.icon = icon + sort_pos = tag_json.get("sort_pos", None) + if icon: + tag.sort_pos = sort_pos + db.session.merge(tag) + db.session.commit() + clean_auth_cache() if not User.query.first(): user = User() diff --git a/dashmachine/main/routes.py b/dashmachine/main/routes.py index fa2aaa6..1e6d0bc 100755 --- a/dashmachine/main/routes.py +++ b/dashmachine/main/routes.py @@ -48,14 +48,20 @@ def home(): @main.route("/app_view?", methods=["GET"]) -def app_view(app_id): +@main.route("/app_view?", methods=["GET"]) +def app_view(app_id, url=None): settings = Settings.query.first() if not check_groups(settings.home_access_groups, current_user): return redirect(url_for("user_system.login")) - app_db = Apps.query.filter_by(id=app_id).first() - return render_template( - "main/app-view.html", url=f"{app_db.prefix}{app_db.url}", title=app_db.name - ) + + if url: + title = url + + if not url: + app_db = Apps.query.filter_by(id=app_id).first() + url = f"{app_db.prefix}{app_db.url}" + title = app_db.name + return render_template("main/app-view.html", url=url, title=title) @main.route("/load_data_source", methods=["GET"]) @@ -65,19 +71,6 @@ def load_data_source(): return data -@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(user.username, "home_view_mode", mode) - config.write(open(os.path.join(user_data_folder, "config.ini"), "w")) - 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() diff --git a/dashmachine/settings_system/models.py b/dashmachine/settings_system/models.py index faac7aa..4c4fa29 100644 --- a/dashmachine/settings_system/models.py +++ b/dashmachine/settings_system/models.py @@ -9,6 +9,6 @@ class Settings(db.Model): roles = db.Column(db.String()) home_access_groups = db.Column(db.String()) 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()) + tags_expanded = db.Column(db.String()) diff --git a/dashmachine/settings_system/utils.py b/dashmachine/settings_system/utils.py index 49baded..7a24899 100644 --- a/dashmachine/settings_system/utils.py +++ b/dashmachine/settings_system/utils.py @@ -18,19 +18,8 @@ def load_files_html(): ) -def get_config_html(): - with open(os.path.join(root_folder, "config_readme.md")) as readme_file: - md = readme_file.read() - platforms = os.listdir(platform_folder) - platforms = sorted(platforms) - for platform in platforms: - name, extension = os.path.splitext(platform) - if extension.lower() == ".py": - module = importlib.import_module(f"dashmachine.platform.{name}", ".") - if module.__doc__: - md += module.__doc__ - - config_html = markdown( +def convert_html_to_md(md): + html = markdown( md, extras=[ "tables", @@ -40,4 +29,28 @@ def get_config_html(): "code-friendly", ], ) - return config_html + return html + + +def get_config_html(): + with open(os.path.join(root_folder, "readme_settings.md")) as readme_file: + md = readme_file.read() + html = {"settings": convert_html_to_md(md)} + + with open(os.path.join(root_folder, "readme_cards.md")) as readme_file: + md = readme_file.read() + html["cards"] = convert_html_to_md(md) + + with open(os.path.join(root_folder, "readme_data_sources.md")) as readme_file: + md = readme_file.read() + platforms = os.listdir(platform_folder) + platforms = sorted(platforms) + for platform in platforms: + name, extension = os.path.splitext(platform) + if extension.lower() == ".py": + module = importlib.import_module(f"dashmachine.platform.{name}", ".") + if module.__doc__: + md += module.__doc__ + html["data_sources"] = convert_html_to_md(md) + + return html diff --git a/dashmachine/sources.py b/dashmachine/sources.py index 0962dfc..ade73ec 100644 --- a/dashmachine/sources.py +++ b/dashmachine/sources.py @@ -1,9 +1,10 @@ import os +import json import random from jsmin import jsmin from flask_login import current_user from dashmachine import app -from dashmachine.main.models import Apps +from dashmachine.main.models import Apps, Tags from dashmachine.main.utils import check_groups, get_update_message_html from dashmachine.main.forms import TagsForm from dashmachine.settings_system.models import Settings @@ -73,24 +74,38 @@ def process_css_sources(process_bundle=None, src=None, app_global=False): return html +def tag_sort_func(e): + return e.sort_pos + + @app.context_processor def context_processor(): apps = [] + temp_tags = [] tags = [] apps_db = Apps.query.all() for app_db in apps_db: + if app_db.urls: + url_list = app_db.urls.replace("},{", "}%,%{").split("%,%") + app_db.urls_json = [] + for url in url_list: + app_db.urls_json.append(json.loads(url)) if not app_db.groups: app_db.groups = None if check_groups(app_db.groups, current_user): apps.append(app_db) if app_db.tags: - tags += app_db.tags.split(",") + temp_tags += app_db.tags.split(",") tags_form = TagsForm() - if len(tags) > 0: - tags = [tag.strip() for tag in tags] - tags = list(dict.fromkeys(tags)) - tags_form.tags.choices += [(tag, tag) for tag in tags] + if len(temp_tags) > 0: + temp_tags = list(dict.fromkeys([tag.strip() for tag in temp_tags])) + tags_form.tags.choices += [(tag, tag) for tag in temp_tags] + for tag in temp_tags: + tag_db = Tags.query.filter_by(name=tag).first() + if tag_db: + tags.append(tag_db) + tags.sort(key=tag_sort_func) settings = Settings.query.first() if settings.background == "random": if len(os.listdir(backgrounds_images_folder)) < 1: @@ -107,6 +122,7 @@ def context_processor(): process_css_sources=process_css_sources, apps=apps, settings=settings, + tags=tags, tags_form=tags_form, update_message=update_message, ) diff --git a/dashmachine/static/css/global/dashmachine.css b/dashmachine/static/css/global/dashmachine.css index fbe7e77..66c6101 100644 --- a/dashmachine/static/css/global/dashmachine.css +++ b/dashmachine/static/css/global/dashmachine.css @@ -399,7 +399,10 @@ input:disabled { border-radius: 8px; background: var(--theme-surface); } - +.card .card-reveal { + border-radius: 8px; + background: var(--theme-surface); +} /* TABS */ .tabs { diff --git a/dashmachine/static/css/main/home.css b/dashmachine/static/css/main/home.css index 97ad099..5da6ba7 100644 --- a/dashmachine/static/css/main/home.css +++ b/dashmachine/static/css/main/home.css @@ -17,11 +17,16 @@ @media screen and (max-width: 992px) { .tags-select-col { top: 0; - width: calc(100vw - 45px) !important; - margin-left: 15px !important; + /*width: calc(100vw - 45px) !important;*/ + /*margin-left: 15px !important;*/ } } +.app-card .card-reveal { + position: + +} + #list-view-collection .app-a { background: rgba(var(--theme-surface-rgb), 0.8); } diff --git a/dashmachine/static/css/settings_system/settings.css b/dashmachine/static/css/settings_system/settings.css index 9dfd525..5134a24 100644 --- a/dashmachine/static/css/settings_system/settings.css +++ b/dashmachine/static/css/settings_system/settings.css @@ -28,18 +28,18 @@ background: var(--theme-surface-1); } -#config-readme h5 { +#settings-readme h5, #cards-readme h5, #data-sources-readme h5 { color: var(--theme-primary); margin-top: 5%; } -#config-readme h4 { +#settings-readme h4, #cards-readme h4, #data-sources-readme h4 { color: var(--theme-color-font-muted); margin-top: 5%; } #configini-readme { margin-top: 2% !important; } -#config-readme code { +#settings-readme code, #cards-readme code, #data-sources-readme code { -webkit-touch-callout: all; -webkit-user-select: all; -khtml-user-select: all; @@ -49,10 +49,10 @@ cursor: text; white-space: pre-wrap; } -#config-readme th { +#settings-readme th, #cards-readme th, #data-sources-readme th { color: var(--theme-primary); } -#config-readme td { +#settings-readme td, #cards-readme td, #data-sources-readme td { -webkit-touch-callout: text !important; -webkit-user-select: text !important; -khtml-user-select: text !important; @@ -61,6 +61,6 @@ user-select: text !important; cursor: text; } -#config-readme strong { +#settings-readme strong, #cards-readme strong, #data-sources-readme strong { font-weight: 900; } \ No newline at end of file diff --git a/dashmachine/static/js/main/home.js b/dashmachine/static/js/main/home.js index 9996269..e3f4574 100644 --- a/dashmachine/static/js/main/home.js +++ b/dashmachine/static/js/main/home.js @@ -1,22 +1,49 @@ var d = document.getElementById("dashboard-sidenav"); d.className += " active theme-primary"; +function get_data_source(el){ + el.html(""); + el.closest('.col').find('.data-source-loading').removeClass('hide'); + $.ajax({ + async: true, + url: el.attr('data-url'), + type: 'GET', + data: {id: el.attr('data-id')}, + success: function(data){ + el.closest('.col').find('.data-source-loading').addClass('hide'); + el.html(data); + } + }); +} + $( document ).ready(function() { $(".tooltipped").tooltip(); $("#apps-filter").on('keyup', function(e) { + $(".toggle-tag-expand-btn").each(function(e) { + if ($(this).attr("data-expanded") == 'false'){ + $(this)[0].click(); + } + }); var value = $(this).val().toLowerCase(); - $(".app-a").each(function(i, e) { - if ($(this).attr("data-name").toLowerCase().indexOf(value) > -1 - || $(this).attr("data-description").toLowerCase().indexOf(value) > -1) { + + $(".app-card").each(function(e) { + var x = 0 + $(this).find('.searchable').each(function(e) { + if ($(this).text().toLowerCase().indexOf(value) > -1) { + x = x + 1 + } + }); + if (x > 0){ $(this).removeClass('hide'); } else { $(this).addClass('hide'); } }); + $(".tag-group").each(function(i, e) { var x = 0 - $(this).find('.app-a').each(function(i, e) { + $(this).find('.app-card').each(function(i, e) { if ($(this).hasClass("hide") === false){ x = x + 1 } @@ -30,22 +57,22 @@ $( document ).ready(function() { }); $(".data-source-container").each(function(e) { - var el = $(this); - $.ajax({ - async: true, - url: el.attr('data-url'), - type: 'GET', - data: {id: el.attr('data-id')}, - success: function(data){ - el.closest('.col').find('.data-source-loading').addClass('hide'); - el.html(data); - } + get_data_source($(this)); + }); + + $(".refresh-data-source-btn").on('click', function(e) { + e.preventDefault(); + $(this).closest('.app-card').find(".data-source-container").each(function(e) { + get_data_source($(this)); }); }); $("#tags-select").on('change', function(e) { var value = $(this).val(); $(".tag-group").each(function(i, e) { + if ($(this).find('.toggle-tag-expand-btn').attr("data-expanded") == "false"){ + $(this).find('.toggle-tag-expand-btn')[0].click(); + } if ($(this).attr("data-tag").indexOf(value) > -1 || value === "All tags") { $(this).removeClass('filtered'); } else { @@ -54,4 +81,52 @@ $( document ).ready(function() { }); }); + $(".toggle-tag-expand-btn").on('click', function(e) { + if ($(this).attr("data-expanded") == "true"){ + $(this).attr("data-expanded", "false"); + $(this).text('keyboard_arrow_down'); + $(this).closest('.tag-group').find('.tag-apps-row').addClass('hide'); + } else { + $(this).attr("data-expanded", "true"); + $(this).text('keyboard_arrow_up'); + $(this).closest('.tag-group').find('.tag-apps-row').removeClass('hide'); + } + var x = 0 + $(".toggle-tag-expand-btn").each(function(e) { + if ($(this).attr("data-expanded") == "true") { + x = x + 1 + } + }); + if (x > 0) { + $("#toggle-tag-expand-all-btn").text('unfold_less'); + } else { + $("#toggle-tag-expand-all-btn").text('unfold_more'); + } + }); + + $("#toggle-tag-expand-all-btn").on('click', function(e) { + if ($(this).text() == "unfold_more") { + $(".toggle-tag-expand-btn").each(function(e) { + $(this)[0].click(); + }); + } else { + $(".toggle-tag-expand-btn").each(function(e) { + if ($(this).attr("data-expanded") == "true"){ + $(this)[0].click(); + } + }); + } + }); + + if ($("#settings-tags_expanded").val() == "False" || $("#user-tags_expanded").val() == "False"){ + $(".toggle-tag-expand-btn").each(function(e) { + $(this)[0].click(); + }); + if ($("#user-tags_expanded").val() == "True"){ + $(".toggle-tag-expand-btn").each(function(e) { + $(this)[0].click(); + }); + } + } + }); \ No newline at end of file diff --git a/dashmachine/static/js/settings_system/settings.js b/dashmachine/static/js/settings_system/settings.js index d1f7e41..9bb0a01 100644 --- a/dashmachine/static/js/settings_system/settings.js +++ b/dashmachine/static/js/settings_system/settings.js @@ -2,7 +2,7 @@ var d = document.getElementById("settings-sidenav"); d.className += " active theme-primary"; $( document ).ready(function() { - $("#config-readme table").addClass('responsive-table'); + $("#settings-readme table").addClass('responsive-table'); initTCdrop('#images-tcdrop'); $("#user-modal").modal({ onCloseEnd: function () { diff --git a/dashmachine/templates/main/base.html b/dashmachine/templates/main/base.html index 75fd999..2c70092 100644 --- a/dashmachine/templates/main/base.html +++ b/dashmachine/templates/main/base.html @@ -52,12 +52,14 @@ + {# User settings from database #} + diff --git a/dashmachine/templates/main/home.html b/dashmachine/templates/main/home.html index d1022a3..d740df9 100644 --- a/dashmachine/templates/main/home.html +++ b/dashmachine/templates/main/home.html @@ -1,6 +1,6 @@ {% extends "main/layout.html" %} {% from 'global_macros.html' import data, preload_circle, select %} -{% from 'main/macros.html' import GridViewApp, ListViewApp %} +{% from 'main/macros.html' import App, Collection, Custom %} {% block page_vendor_css %} {% endblock page_vendor_css %} @@ -23,26 +23,16 @@
-
+
search - {% if current_user.is_authenticated %} - {% if current_user.home_view_mode == "list" %} - - apps - - {% else %} - - list - - {% endif %} - {% endif %} + unfold_less
{% if tags_form.tags.choices|count > 1 %} -
+
{{ tags_form.tags(id='tags-select') }}
{% endif %} @@ -50,69 +40,47 @@
{% if apps %} {# If tags are enabled, render the apps like this #} - {% if tags_form.tags.choices|count > 1 %} - {% if settings.home_view_mode == "list" or current_user.home_view_mode == "list" %} - -
-
- {% for tag in tags_form.tags.choices %} - {% if tag[0] != 'All tags' %} -
- {{ tag[0] }} - {% for app in apps %} - {% if app.tags and tag[0] in app.tags %} - {{ ListViewApp(app) }} + {% if tags|count > 1 %} + {% for tag in tags %} +
+
+
+
+
+
+
+ {% if tag.icon %} + {{ tag.icon }} {% endif %} - {% endfor %} + {{ tag.name }} + keyboard_arrow_up +
+
+
+
+
+ {% for app in apps %} + {% if app.tags and tag.name in app.tags %} + {% if app.type == "app" %} + {{ App(app) }} + {% elif app.type == "collection" %} + {{ Collection(app) }} + {% elif app.type == "custom" %} + {{ Custom(app) }} + {% endif %} + {% endif %} {% endfor %}
- - {% else %} - {% for tag in tags_form.tags.choices %} - {% if tag[0] != 'All tags' %} -
-
-
-
-
{{ tag[0] }}
-
-
-
-
- {% for app in apps %} - {% if app.tags and tag[0] in app.tags %} - {{ GridViewApp(app) }} - {% endif %} - {% endfor %} -
-
-
- {% endif %} - {% endfor %} - {% endif %} - + {% endfor %} {% else %} {# otherwise, render the apps like this #} - {% if settings.home_view_mode == "list" or current_user.home_view_mode == "list" %} -
-
- {% for app in apps %} - {{ ListViewApp(app) }} - {% endfor %} -
-
- - {% else %} - {% for app in apps %} - {{ GridViewApp(app) }} - {% endfor %} - {% endif %} + {% for app in apps %} + {{ GridViewApp(app) }} + {% endfor %} {% endif %} - - {% else %}
diff --git a/dashmachine/templates/main/layout.html b/dashmachine/templates/main/layout.html index d793ac7..297e503 100644 --- a/dashmachine/templates/main/layout.html +++ b/dashmachine/templates/main/layout.html @@ -43,11 +43,13 @@ {% for app in apps %} -
  • - {{ AppAnchor(app) }} - - {{ app.name }} -
  • + {% if app.type == "app" and app.sidebar_icon %} +
  • + {{ AppAnchor(app) }} + + {{ app.name }} +
  • + {% endif %} {% endfor %} diff --git a/dashmachine/templates/main/macros.html b/dashmachine/templates/main/macros.html index 435a25c..8cedccd 100644 --- a/dashmachine/templates/main/macros.html +++ b/dashmachine/templates/main/macros.html @@ -1,86 +1,116 @@ {% from 'global_macros.html' import preload_circle %} -{% macro AppAnchor(app, classes=None) %} - {% if app.open_in == 'iframe' %} +{% macro AppAnchor(app, classes=None, override=None) %} + {% if override == 'iframe' or app.open_in == 'iframe' and override == None %} - {% elif app.open_in == 'this_tab' %} + {% elif override == 'this_tab' or app.open_in == 'this_tab' and override == None %} - {% elif app.open_in == "new_tab" %} + {% elif override == 'new_tab' or app.open_in == "new_tab" and override == None %} {% endif %} {% endmacro %} -{% macro GridViewApp(app) %} - {{ AppAnchor(app) }} -
    -
    -
    - {% if app.data_sources.count() > 0 %} -
    -
    - -
    +{% macro App(app) %} +
    +
    + {{ AppAnchor(app) }} +
    + {% if app.data_sources.count() > 0 %} +
    +
    + +
    -
    - {{ preload_circle() }} - {% for data_source in app.data_sources %} -

    -

    - {% endfor %} +
    +
    +
    +
    + {% for data_source in app.data_sources %} +

    +

    + {% endfor %} +
    -
    - {% else %} - - {% endif %} + {% else %} + + {% endif %} +
    +
    +
    + {{ app.name }} + more_vert + {% if app.data_sources.count() > 0 %} + refresh + {% endif %} + +
    -
    -
    {{ app.name }} -
    +
    + {{ app.name }}close {% if app.description %} - {{ app.description }} - {% else %} - +

    {{ app.description|safe }}

    {% endif %}
    - - {# This closes AppAnchor() #} {% endmacro %} -{% macro ListViewApp(app) %} - {{ AppAnchor(app, classes="collection-item") }} -
    -
    - - {{ app.name }} - {% if app.description %} - info - {% endif %} -
    -
    - {% if app.data_sources.count() > 0 %} - {{ preload_circle() }} - {% for data_source in app.data_sources %} - - - {% endfor %} - {% endif %} +{% macro Collection(app) %} +
    +
    +
    + + {% if app.icon %} + {{app.icon}} + {% endif %} + {{ app.name }} + +
    + {% for url in app.urls_json %} + +
    + {% if url['icon'] %} + + {% endif %} + {% if url['open_in'] == 'this_tab' %} + + {% else %} + + {% endif %} + {{ url['name'] }} + +
    + + {% endfor %} +
    +
    +
    +
    +{% endmacro %} + +{% macro Custom(app) %} +
    +
    +
    {{ app.name }}
    +
    +
    +
    +
    +
    + {% for data_source in app.data_sources %} +

    +

    + {% endfor %} +
    +
    - - {# This closes AppAnchor() #} {% endmacro %} \ No newline at end of file diff --git a/dashmachine/templates/settings_system/settings.html b/dashmachine/templates/settings_system/settings.html index 47eaaa9..d93d4db 100644 --- a/dashmachine/templates/settings_system/settings.html +++ b/dashmachine/templates/settings_system/settings.html @@ -63,18 +63,22 @@
    -
    -
    App Templates
    +
    + {{ config_readme['settings']|safe }} +
    + +
    +

    Cards

    +
    Card Templates
    + Search for preconfigured cards included with DashMachine.
    @@ -105,10 +115,11 @@
    + {{ config_readme['cards']|safe }}
    -
    - {{ config_readme|safe }} +
    + {{ config_readme['data_sources']|safe }}
    diff --git a/dashmachine/user_system/models.py b/dashmachine/user_system/models.py index cc13132..0626187 100644 --- a/dashmachine/user_system/models.py +++ b/dashmachine/user_system/models.py @@ -15,5 +15,5 @@ class User(db.Model, UserMixin): theme = db.Column(db.String()) background = db.Column(db.String()) accent = db.Column(db.String()) - home_view_mode = db.Column(db.String(), default="grid") sidebar_default = db.Column(db.String()) + tags_expanded = db.Column(db.String()) diff --git a/default_config.ini b/default_config.ini index a4f75c8..81efc11 100644 --- a/default_config.ini +++ b/default_config.ini @@ -5,7 +5,6 @@ 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 diff --git a/readme_cards.md b/readme_cards.md new file mode 100644 index 0000000..7d0494b --- /dev/null +++ b/readme_cards.md @@ -0,0 +1,61 @@ + +##### Apps +These entries are the standard card type for displaying apps on your dashboard and sidenav. +```ini +[App Name] +prefix = https:// +url = your-website.com +icon = static/images/apps/default.png +sidebar_icon = static/images/apps/default.png +description = Example description +open_in = iframe +data_sources = None +tags = Example Tag +groups = admin_only +``` + +| Variable | Required | Description | Options | +|--------------|----------|-------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| [App Name] | Yes | The name of your app. | [App Name] | +| prefix | Yes | The prefix for the app's url. | web prefix, e.g. http:// or https:// | +| url | Yes | The url for your app. | web url, e.g. myapp.com | +| open_in | Yes | open the app in the current tab, an iframe or a new tab | iframe, new_tab, this_tab | +| icon | Yes | Icon for the dashboard. | /static/images/icons/yourpicture.png, external link to image | +| sidebar_icon | No | Icon for the sidenav. | /static/images/icons/yourpicture.png, external link to image | +| description | No | A short description for the app. | string | +| data_sources | No | Data sources to be included on the app's card.*Note: you must have a data source set up in the config above this application entry. | comma separated string | +| tags | No | Optionally specify tags for organization on /home | comma separated string | +| groups | No | Optionally specify the access groups that can see this app. | comma separated string | + +##### Collection +These entries provide a card on the dashboard containing a list of links. +```ini +[Collection Name] +type = collection +icon = collections_bookmark +urls = {"url": "google.com", "icon": "static/images/apps/default.png", "name": "Google", "open_in": "new_tab"},{"url": "duckduckgo.com", "icon": "static/images/apps/default.png", "name": "DuckDuckGo", "open_in": "iframe"} +``` + +| Variable | Required | Description | Options | +|-------------------|----------|----------------------------------------------------------------------------------------------|-------------------------------------| +| [Collection Name] | Yes | Name for the collection | [Collection Name] | +| type | Yes | This tells DashMachine what type of card this is. | collection | +| icon | No | The material design icon class for the collection. | https://material.io/resources/icons | +| urls | Yes | The urls to include in your collection. Json options are "url", "icon", "name" and "open_in" | comma separated json dicts, "open_in" only has options "this_tab", "new_tab" | +| tags | No | Optionally specify tags for organization on /home | comma separated string | +| groups | No | Optionally specify the access groups that can see this app. | comma separated string | + +##### Custom Card +These entries provide an empty card on the dashboard to be populated by a data source. This allows the data source to populate the entire card. +```ini +[Collection Name] +type = custom +data_sources = my_data_source +``` + +| Variable | Required | Description | Options | +|-------------------|----------|----------------------------------------------------------------------------------------------|-------------------------------------| +| [Collection Name] | Yes | Name for the collection | [Collection Name] | +| type | Yes | This tells DashMachine what type of card this is. | custom | +| tags | No | Optionally specify tags for organization on /home | comma separated string | +| groups | No | Optionally specify the access groups that can see this app. | comma separated string | \ No newline at end of file diff --git a/readme_data_sources.md b/readme_data_sources.md new file mode 100644 index 0000000..0d184ae --- /dev/null +++ b/readme_data_sources.md @@ -0,0 +1,6 @@ +#### 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! + +> 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: +`data_source = variable_name` \ No newline at end of file diff --git a/readme_settings.md b/readme_settings.md new file mode 100644 index 0000000..4b0f788 --- /dev/null +++ b/readme_settings.md @@ -0,0 +1,95 @@ +#### Main Settings + +##### 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. +```ini +[Settings] +theme = light +accent = orange +background = None +roles = admin,user,public_user +home_access_groups = admin_only +settings_access_groups = admin_only +custom_app_title = DashMachine +sidebar_default = open +tags_expanded = True +``` + +| Variable | Required | Description | Options | +|------------------------|----------|----------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [Settings] | Yes | Config section name. | [Settings] | +| theme | Yes | UI theme. | light, dark | +| accent | Yes | UI accent color | orange, red, pink, purple, deepPurple, indigo, blue, lightBlue,cyan, teal, green, lightGreen, lime, yellow, amber, deepOrange, brown, grey, blueGrey | +| background | Yes | Background image for the UI | /static/images/backgrounds/yourpicture.png, external link to image, None, random | +| 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 | +| 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 | +| tags | No | Set custom options for your tags. Json options are "name", "icon", "sort_pos" | comma separated json dicts. For "icon" use material design icons: https://material.io/resources/icons | +| tags_expanded | No | Set to False to have your tags collapsed by default | True, False | + +##### 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. +```ini +[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 | +| 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 | + +##### 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 +```ini +[admin_only] +roles = admin +``` + +| Variable | Required | Description | Options | +|--------------|----------|--------------------------------------------------------------------------------|----------------------------------------------------------------------------------| +| [Group Name] | Yes | Name for access group. | [Group Name] | +| roles | Yes | A comma separated list of user roles allowed to view apps in this access group | Roles defined in your config. If not defined, defaults are admin and public_user | + +> Say we wanted to create a limited user that still has a login, but can only access `/home` and certain apps we would first create a group: +>```ini +>[users] +>roles = admin, user +>``` +>then we would change in the `[Settings]` entry: +>```ini +>home_access_groups = users +>``` +>By default here, the `user` user could access `/home`, but would see no apps. To allow access, we would add to apps: +>```ini +>groups = users +>``` +>Say we then wanted to allow some access for users without a login (`public_user`), we would add: +>```ini +>[public] +>roles = admin, user, public_user +>``` +>then we would change in the `[Settings]` entry: +>```ini +>home_access_groups = public +>``` +>By default here, the `public_user` user could access `/home`, but would see no apps. To allow access, we would add to apps: +>```ini +>groups = public +>``` + + +>It’s also important to note, when setting up roles in `[Settings]`, say we had roles set like this: +>```ini +>roles = my_people +>``` +>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.