- reduced the size of app cards, moving description to a pop-open

- broke up config readme into 3 tabs, and 3 .md files
- changed 'app templates' to 'card templates'
- added 'collection' cards
- added 'custom' cards
- added options for setting tag icons and sort position
- removed list view to focus on different card types on /home
- added ability to collapse/expand tags on /home
- added setting for having tags default to collapsed state
- added settings for the default state of the sidebar
- created a public user view with no sidebar
- added sidebar default overrides for users
This commit is contained in:
Ross Mountjoy 2020-03-27 08:58:11 -04:00
parent c28c29c2e0
commit ff6b2372b3
22 changed files with 574 additions and 271 deletions

View File

@ -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 ##### 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.
@ -10,7 +18,6 @@ background = None
roles = admin,user,public_user roles = admin,user,public_user
home_access_groups = admin_only home_access_groups = admin_only
settings_access_groups = admin_only settings_access_groups = admin_only
home_view_mode = grid
custom_app_title = DashMachine custom_app_title = DashMachine
sidebar_default = open 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. | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 ##### 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
@ -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. >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 #### 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!

View File

@ -28,6 +28,8 @@ class Apps(db.Model):
data_template = db.Column(db.String()) data_template = db.Column(db.String())
groups = db.Column(db.String()) groups = db.Column(db.String())
tags = db.Column(db.String()) tags = db.Column(db.String())
type = db.Column(db.String())
urls = db.Column(db.String())
class DataSources(db.Model): class DataSources(db.Model):
@ -58,3 +60,5 @@ class Groups(db.Model):
class Tags(db.Model): class Tags(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String()) name = db.Column(db.String())
icon = db.Column(db.String())
sort_pos = db.Column(db.Integer)

View File

@ -1,4 +1,5 @@
import os import os
import json
from configparser import ConfigParser from configparser import ConfigParser
from dashmachine.main.models import Apps, Groups, DataSources, DataSourcesArgs, Tags from dashmachine.main.models import Apps, Groups, DataSources, DataSourcesArgs, Tags
from dashmachine.user_system.models import User from dashmachine.user_system.models import User
@ -68,7 +69,6 @@ def read_config():
settings.settings_access_groups = config["Settings"].get( settings.settings_access_groups = config["Settings"].get(
"settings_access_groups", "admin_only" "settings_access_groups", "admin_only"
) )
settings.home_view_mode = config["Settings"].get("home_view_mode", "grid")
settings.custom_app_title = config["Settings"].get( settings.custom_app_title = config["Settings"].get(
"custom_app_title", "DashMachine" "custom_app_title", "DashMachine"
@ -76,6 +76,8 @@ def read_config():
settings.sidebar_default = config["Settings"].get("sidebar_default", "open") settings.sidebar_default = config["Settings"].get("sidebar_default", "open")
settings.tags_expanded = config["Settings"].get("tags_expanded", "True")
db.session.add(settings) db.session.add(settings)
db.session.commit() db.session.commit()
@ -85,9 +87,9 @@ def read_config():
user.username = section user.username = section
user.role = config[section]["role"] user.role = config[section]["role"]
user.sidebar_default = config[section].get("sidebar_default", None) 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.theme = config[section].get("theme", None)
user.accent = config[section].get("accent", None) user.accent = config[section].get("accent", None)
user.tags_expanded = config[section].get("tags_expanded", None)
user.password = "" user.password = ""
if not User.query.filter_by(role="admin").first() and user.role != "admin": if not User.query.filter_by(role="admin").first() and user.role != "admin":
print( print(
@ -146,35 +148,25 @@ def read_config():
# App creation # App creation
app = Apps() app = Apps()
app.name = section app.name = section
if "prefix" in config[section]: app.type = config[section].get("type", "app")
app.prefix = config[section]["prefix"]
else: 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."} return {"msg": f"Invalid Config: {section} does not contain prefix."}
if "url" in config[section]: app.url = config[section].get("url", None)
app.url = config[section]["url"] if app.type == "app" and not app.url:
else:
return {"msg": f"Invalid Config: {section} does not contain url."} return {"msg": f"Invalid Config: {section} does not contain url."}
if "icon" in config[section]: app.icon = config[section].get("icon", None)
app.icon = config[section]["icon"]
else:
app.icon = None
if "sidebar_icon" in config[section]: app.sidebar_icon = config[section].get("sidebar_icon", None)
app.sidebar_icon = config[section]["sidebar_icon"]
else:
app.sidebar_icon = app.icon
if "description" in config[section]: app.description = config[section].get("description", None)
app.description = config[section]["description"]
else:
app.description = None
if "open_in" in config[section]: app.open_in = config[section].get("open_in", "this_tab")
app.open_in = config[section]["open_in"]
else: app.urls = config[section].get("urls", None)
app.open_in = "this_tab"
if "groups" in config[section]: if "groups" in config[section]:
for group_name in config[section]["groups"].split(","): for group_name in config[section]["groups"].split(","):
@ -186,14 +178,18 @@ def read_config():
else: else:
app.groups = None app.groups = None
# Tags creation
if "tags" in config[section]: if "tags" in config[section]:
app.tags = config[section]["tags"].title() app.tags = config[section]["tags"]
for tag in app.tags.split(","): for tag in app.tags.split(","):
tag = tag.strip().title() tag = tag.strip()
if not Tags.query.filter_by(name=tag).first(): if not Tags.query.filter_by(name=tag).first():
tag_db = Tags(name=tag) tag_db = Tags(name=tag)
db.session.add(tag_db) db.session.add(tag_db)
db.session.commit() db.session.commit()
tag_db.sort_pos = tag_db.id
db.session.merge(tag_db)
db.session.commit()
else: else:
if Tags.query.first(): if Tags.query.first():
app.tags = "Untagged" app.tags = "Untagged"
@ -227,6 +223,23 @@ def read_config():
db.session.add(group) db.session.add(group)
db.session.commit() 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() clean_auth_cache()
if not User.query.first(): if not User.query.first():
user = User() user = User()

View File

@ -48,14 +48,20 @@ def home():
@main.route("/app_view?<app_id>", methods=["GET"]) @main.route("/app_view?<app_id>", methods=["GET"])
def app_view(app_id): @main.route("/app_view?<url>", methods=["GET"])
def app_view(app_id, url=None):
settings = Settings.query.first() settings = Settings.query.first()
if not check_groups(settings.home_access_groups, current_user): if not check_groups(settings.home_access_groups, current_user):
return redirect(url_for("user_system.login")) return redirect(url_for("user_system.login"))
app_db = Apps.query.filter_by(id=app_id).first()
return render_template( if url:
"main/app-view.html", url=f"{app_db.prefix}{app_db.url}", title=app_db.name 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"]) @main.route("/load_data_source", methods=["GET"])
@ -65,19 +71,6 @@ def load_data_source():
return data return data
@main.route("/change_home_view_mode?<mode>%<user_id>", 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"]) @main.route("/update_message_read", methods=["GET"])
def update_message_read(): def update_message_read():
mark_update_message_read() mark_update_message_read()

View File

@ -9,6 +9,6 @@ class Settings(db.Model):
roles = db.Column(db.String()) roles = db.Column(db.String())
home_access_groups = db.Column(db.String()) home_access_groups = db.Column(db.String())
settings_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()) custom_app_title = db.Column(db.String())
sidebar_default = db.Column(db.String()) sidebar_default = db.Column(db.String())
tags_expanded = db.Column(db.String())

View File

@ -18,19 +18,8 @@ def load_files_html():
) )
def get_config_html(): def convert_html_to_md(md):
with open(os.path.join(root_folder, "config_readme.md")) as readme_file: html = markdown(
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(
md, md,
extras=[ extras=[
"tables", "tables",
@ -40,4 +29,28 @@ def get_config_html():
"code-friendly", "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

View File

@ -1,9 +1,10 @@
import os import os
import json
import random import random
from jsmin import jsmin from jsmin import jsmin
from flask_login import current_user from flask_login import current_user
from dashmachine import app 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.utils import check_groups, get_update_message_html
from dashmachine.main.forms import TagsForm from dashmachine.main.forms import TagsForm
from dashmachine.settings_system.models import Settings 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 return html
def tag_sort_func(e):
return e.sort_pos
@app.context_processor @app.context_processor
def context_processor(): def context_processor():
apps = [] apps = []
temp_tags = []
tags = [] tags = []
apps_db = Apps.query.all() apps_db = Apps.query.all()
for app_db in apps_db: 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: if not app_db.groups:
app_db.groups = None app_db.groups = None
if check_groups(app_db.groups, current_user): if check_groups(app_db.groups, current_user):
apps.append(app_db) apps.append(app_db)
if app_db.tags: if app_db.tags:
tags += app_db.tags.split(",") temp_tags += app_db.tags.split(",")
tags_form = TagsForm() tags_form = TagsForm()
if len(tags) > 0: if len(temp_tags) > 0:
tags = [tag.strip() for tag in tags] temp_tags = list(dict.fromkeys([tag.strip() for tag in temp_tags]))
tags = list(dict.fromkeys(tags)) tags_form.tags.choices += [(tag, tag) for tag in temp_tags]
tags_form.tags.choices += [(tag, tag) for tag in 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() settings = Settings.query.first()
if settings.background == "random": if settings.background == "random":
if len(os.listdir(backgrounds_images_folder)) < 1: if len(os.listdir(backgrounds_images_folder)) < 1:
@ -107,6 +122,7 @@ def context_processor():
process_css_sources=process_css_sources, process_css_sources=process_css_sources,
apps=apps, apps=apps,
settings=settings, settings=settings,
tags=tags,
tags_form=tags_form, tags_form=tags_form,
update_message=update_message, update_message=update_message,
) )

View File

@ -399,7 +399,10 @@ input:disabled {
border-radius: 8px; border-radius: 8px;
background: var(--theme-surface); background: var(--theme-surface);
} }
.card .card-reveal {
border-radius: 8px;
background: var(--theme-surface);
}
/* TABS */ /* TABS */
.tabs { .tabs {

View File

@ -17,11 +17,16 @@
@media screen and (max-width: 992px) { @media screen and (max-width: 992px) {
.tags-select-col { .tags-select-col {
top: 0; top: 0;
width: calc(100vw - 45px) !important; /*width: calc(100vw - 45px) !important;*/
margin-left: 15px !important; /*margin-left: 15px !important;*/
} }
} }
.app-card .card-reveal {
position:
}
#list-view-collection .app-a { #list-view-collection .app-a {
background: rgba(var(--theme-surface-rgb), 0.8); background: rgba(var(--theme-surface-rgb), 0.8);
} }

View File

@ -28,18 +28,18 @@
background: var(--theme-surface-1); background: var(--theme-surface-1);
} }
#config-readme h5 { #settings-readme h5, #cards-readme h5, #data-sources-readme h5 {
color: var(--theme-primary); color: var(--theme-primary);
margin-top: 5%; margin-top: 5%;
} }
#config-readme h4 { #settings-readme h4, #cards-readme h4, #data-sources-readme h4 {
color: var(--theme-color-font-muted); color: var(--theme-color-font-muted);
margin-top: 5%; margin-top: 5%;
} }
#configini-readme { #configini-readme {
margin-top: 2% !important; margin-top: 2% !important;
} }
#config-readme code { #settings-readme code, #cards-readme code, #data-sources-readme code {
-webkit-touch-callout: all; -webkit-touch-callout: all;
-webkit-user-select: all; -webkit-user-select: all;
-khtml-user-select: all; -khtml-user-select: all;
@ -49,10 +49,10 @@
cursor: text; cursor: text;
white-space: pre-wrap; white-space: pre-wrap;
} }
#config-readme th { #settings-readme th, #cards-readme th, #data-sources-readme th {
color: var(--theme-primary); color: var(--theme-primary);
} }
#config-readme td { #settings-readme td, #cards-readme td, #data-sources-readme td {
-webkit-touch-callout: text !important; -webkit-touch-callout: text !important;
-webkit-user-select: text !important; -webkit-user-select: text !important;
-khtml-user-select: text !important; -khtml-user-select: text !important;
@ -61,6 +61,6 @@
user-select: text !important; user-select: text !important;
cursor: text; cursor: text;
} }
#config-readme strong { #settings-readme strong, #cards-readme strong, #data-sources-readme strong {
font-weight: 900; font-weight: 900;
} }

View File

@ -1,22 +1,49 @@
var d = document.getElementById("dashboard-sidenav"); var d = document.getElementById("dashboard-sidenav");
d.className += " active theme-primary"; 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() { $( document ).ready(function() {
$(".tooltipped").tooltip(); $(".tooltipped").tooltip();
$("#apps-filter").on('keyup', function(e) { $("#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(); var value = $(this).val().toLowerCase();
$(".app-a").each(function(i, e) {
if ($(this).attr("data-name").toLowerCase().indexOf(value) > -1 $(".app-card").each(function(e) {
|| $(this).attr("data-description").toLowerCase().indexOf(value) > -1) { 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'); $(this).removeClass('hide');
} else { } else {
$(this).addClass('hide'); $(this).addClass('hide');
} }
}); });
$(".tag-group").each(function(i, e) { $(".tag-group").each(function(i, e) {
var x = 0 var x = 0
$(this).find('.app-a').each(function(i, e) { $(this).find('.app-card').each(function(i, e) {
if ($(this).hasClass("hide") === false){ if ($(this).hasClass("hide") === false){
x = x + 1 x = x + 1
} }
@ -30,22 +57,22 @@ $( document ).ready(function() {
}); });
$(".data-source-container").each(function(e) { $(".data-source-container").each(function(e) {
var el = $(this); get_data_source($(this));
$.ajax({ });
async: true,
url: el.attr('data-url'), $(".refresh-data-source-btn").on('click', function(e) {
type: 'GET', e.preventDefault();
data: {id: el.attr('data-id')}, $(this).closest('.app-card').find(".data-source-container").each(function(e) {
success: function(data){ get_data_source($(this));
el.closest('.col').find('.data-source-loading').addClass('hide');
el.html(data);
}
}); });
}); });
$("#tags-select").on('change', function(e) { $("#tags-select").on('change', function(e) {
var value = $(this).val(); var value = $(this).val();
$(".tag-group").each(function(i, e) { $(".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") { if ($(this).attr("data-tag").indexOf(value) > -1 || value === "All tags") {
$(this).removeClass('filtered'); $(this).removeClass('filtered');
} else { } 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();
});
}
}
}); });

View File

@ -2,7 +2,7 @@ var d = document.getElementById("settings-sidenav");
d.className += " active theme-primary"; d.className += " active theme-primary";
$( document ).ready(function() { $( document ).ready(function() {
$("#config-readme table").addClass('responsive-table'); $("#settings-readme table").addClass('responsive-table');
initTCdrop('#images-tcdrop'); initTCdrop('#images-tcdrop');
$("#user-modal").modal({ $("#user-modal").modal({
onCloseEnd: function () { onCloseEnd: function () {

View File

@ -52,12 +52,14 @@
<input id="settings-theme" class="hide" value="{{ settings.theme }}"> <input id="settings-theme" class="hide" value="{{ settings.theme }}">
<input id="settings-accent" class="hide" value="{{ settings.accent }}"> <input id="settings-accent" class="hide" value="{{ settings.accent }}">
<input id="settings-sidebar_default" class="hide" value="{{ settings.sidebar_default }}"> <input id="settings-sidebar_default" class="hide" value="{{ settings.sidebar_default }}">
<input id="settings-tags_expanded" class="hide" value="{{ settings.tags_expanded }}">
{# User settings from database #} {# User settings from database #}
<input id="user-name" class="hide" value="{{ current_user.username }}"> <input id="user-name" class="hide" value="{{ current_user.username }}">
<input id="user-theme" class="hide" value="{{ current_user.theme }}"> <input id="user-theme" class="hide" value="{{ current_user.theme }}">
<input id="user-accent" class="hide" value="{{ current_user.accent }}"> <input id="user-accent" class="hide" value="{{ current_user.accent }}">
<input id="user-sidebar_default" class="hide" value="{{ current_user.sidebar_default }}"> <input id="user-sidebar_default" class="hide" value="{{ current_user.sidebar_default }}">
<input id="user-tags_expanded" class="hide" value="{{ current_user.tags_expanded }}">
<script src="static/js/vendors/jquery.min.js"></script> <script src="static/js/vendors/jquery.min.js"></script>

View File

@ -1,6 +1,6 @@
{% extends "main/layout.html" %} {% extends "main/layout.html" %}
{% from 'global_macros.html' import data, preload_circle, select %} {% 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 %} {% block page_vendor_css %}
{% endblock page_vendor_css %} {% endblock page_vendor_css %}
@ -23,26 +23,16 @@
<div id="main" class="main-full"> <div id="main" class="main-full">
<div class="container"> <div class="container">
<div class="row card-filter-container"> <div class="row card-filter-container">
<div class="col s12 l4 input-field"> <div class="col s12 m12 l6 xl4 input-field">
<span> <span>
<i class="material-icons prefix card-search-icon">search</i> <i class="material-icons prefix card-search-icon">search</i>
<input type="text" id="apps-filter" class="card-filter theme-surface-transparent" placeholder="Search apps" autofocus> <input type="text" id="apps-filter" class="card-filter theme-surface-transparent" placeholder="Search apps" autofocus>
{% if current_user.is_authenticated %} <i id="toggle-tag-expand-all-btn" class="material-icons right filter-action pointer">unfold_less</i>
{% if current_user.home_view_mode == "list" %}
<a href="{{ url_for('main.change_home_view_mode', mode="grid", user_id=current_user.id) }}">
<i class="material-icons right filter-action pointer">apps</i>
</a>
{% else %}
<a href="{{ url_for('main.change_home_view_mode', mode="list", user_id=current_user.id) }}">
<i class="material-icons right filter-action pointer">list</i>
</a>
{% endif %}
{% endif %}
</span> </span>
</div> </div>
{% if tags_form.tags.choices|count > 1 %} {% if tags_form.tags.choices|count > 1 %}
<div class="input-field col s12 l2 tags-select-col theme-surface-transparent"> <div class="input-field col s6 m4 l2 offset-s3 offset-m4 tags-select-col theme-surface-transparent">
{{ tags_form.tags(id='tags-select') }} {{ tags_form.tags(id='tags-select') }}
</div> </div>
{% endif %} {% endif %}
@ -50,69 +40,47 @@
<div class="row"> <div class="row">
{% if apps %} {% if apps %}
{# If tags are enabled, render the apps like this #} {# If tags are enabled, render the apps like this #}
{% if tags_form.tags.choices|count > 1 %} {% if tags|count > 1 %}
{% if settings.home_view_mode == "list" or current_user.home_view_mode == "list" %} {% for tag in tags %}
<div class="tag-group" data-tag="{{ tag.name }}">
<div class="col s12 m12 l8"> <div class="divider"></div>
<div id="list-view-collection" class="collection"> <div class="row">
{% for tag in tags_form.tags.choices %} <div class="col s12 m12 l6 xl4">
{% if tag[0] != 'All tags' %} <div class="card theme-surface-transparent">
<div class="tag-group" data-tag="{{ tag[0] }}"> <div class="card-content pt-1 pb-1">
<a class="collection-item font-weight-600 theme-on-primary-text theme-primary" style="font-size: 1.2em">{{ tag[0] }}</a> <h5 class="">
{% for app in apps %} {% if tag.icon %}
{% if app.tags and tag[0] in app.tags %} <i class="material-icons-outlined mr-2 theme-primary-text" style="position: relative; top: .2rem">{{ tag.icon }}</i>
{{ ListViewApp(app) }}
{% endif %} {% endif %}
{% endfor %} {{ tag.name }}
<i class="material-icons-outlined theme-secondary-text icon-btn toggle-tag-expand-btn right" data-expanded="true">keyboard_arrow_up</i>
</h5>
</div> </div>
</div>
</div>
</div>
<div class="row tag-apps-row">
{% 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 %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endfor %}
{% else %}
{% for tag in tags_form.tags.choices %}
{% if tag[0] != 'All tags' %}
<div class="tag-group" data-tag="{{ tag[0] }}">
<div class="row">
<div class="col s12 m6 l2">
<div class="card center-align theme-primary">
<h5 class="theme-on-primary-text">{{ tag[0] }}</h5>
</div>
</div>
</div>
<div class="row">
{% for app in apps %}
{% if app.tags and tag[0] in app.tags %}
{{ GridViewApp(app) }}
{% endif %}
{% endfor %}
</div>
<div class="divider"></div>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% else %} {% else %}
{# otherwise, render the apps like this #} {# otherwise, render the apps like this #}
{% if settings.home_view_mode == "list" or current_user.home_view_mode == "list" %} {% for app in apps %}
<div class="col s12 m12 l8"> {{ GridViewApp(app) }}
<div id="list-view-collection" class="collection"> {% endfor %}
{% for app in apps %}
{{ ListViewApp(app) }}
{% endfor %}
</div>
</div>
{% else %}
{% for app in apps %}
{{ GridViewApp(app) }}
{% endfor %}
{% endif %}
{% endif %} {% endif %}
{% else %} {% else %}
<a href="{{ url_for('settings_system.settings') }}"> <a href="{{ url_for('settings_system.settings') }}">
<div class="col s12 m6 l3"> <div class="col s12 m6 l3">

View File

@ -43,11 +43,13 @@
</a></li> </a></li>
{% for app in apps %} {% for app in apps %}
<li class="bold"> {% if app.type == "app" and app.sidebar_icon %}
{{ AppAnchor(app) }} <li class="bold">
<img src="{{ app.sidebar_icon }}" style="position: relative; top: 5px; left: 2px; margin-right: 18px; height: 24px"> {{ AppAnchor(app) }}
<span class="menu-title" data-i18n="">{{ app.name }}</span> <img src="{{ app.sidebar_icon }}" style="position: relative; top: 5px; left: 2px; margin-right: 18px; height: 24px">
</a></li> <span class="menu-title" data-i18n="">{{ app.name }}</span>
</a></li>
{% endif %}
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -1,86 +1,116 @@
{% from 'global_macros.html' import preload_circle %} {% from 'global_macros.html' import preload_circle %}
{% macro AppAnchor(app, classes=None) %} {% macro AppAnchor(app, classes=None, override=None) %}
{% if app.open_in == 'iframe' %} {% if override == 'iframe' or app.open_in == 'iframe' and override == None %}
<a href="{{ url_for('main.app_view', app_id=app.id) }}" class="app-a {{ classes }}" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}"> <a href="{{ url_for('main.app_view', app_id=app.id) }}" class="app-a {{ classes }}" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}">
{% elif app.open_in == 'this_tab' %} {% elif override == 'this_tab' or app.open_in == 'this_tab' and override == None %}
<a href="{{ app.prefix }}{{ app.url }}" class="app-a {{ classes }}" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}"> <a href="{{ app.prefix }}{{ app.url }}" class="app-a {{ classes }}" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}">
{% elif app.open_in == "new_tab" %} {% elif override == 'new_tab' or app.open_in == "new_tab" and override == None %}
<a href="{{ app.prefix }}{{ app.url }}" target="_blank" class="app-a {{ classes }}" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}"> <a href="{{ app.prefix }}{{ app.url }}" target="_blank" class="app-a {{ classes }}" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}">
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro GridViewApp(app) %} {% macro App(app) %}
{{ AppAnchor(app) }} <div class="col s12 m6 l4 xl3 app-card">
<div class="col s12 m6 l3"> <div class="card theme-surface-transparent">
<div class="card theme-surface-transparent app-card"> {{ AppAnchor(app) }}
<div class="card-content center-align scrollbar" style="max-height: 118px; min-height: 118px; scrollbar-width: none;"> <div class="card-content center-align scrollbar pt-3 pb-0" style="max-height: 86px; min-height: 86px; scrollbar-width: none;">
{% if app.data_sources.count() > 0 %} {% if app.data_sources.count() > 0 %}
<div class="row"> <div class="row">
<div class="col s6 center-align"> <div class="col s6 center-align">
<img src="{{ app.icon }}" height="64px"> <img src="{{ app.icon }}" height="64px">
</div> </div>
<div class="col s6 left-align"> <div class="col s6 left-align">
<span class="data-source-loading">{{ preload_circle() }}</span> <div class="progress data-source-loading">
{% for data_source in app.data_sources %} <div class="indeterminate"></div>
<p class="data-source-container" </div>
data-url="{{ url_for('main.load_data_source') }}" {% for data_source in app.data_sources %}
data-id="{{ data_source.id }}"> <p class="data-source-container"
</p> data-url="{{ url_for('main.load_data_source') }}"
{% endfor %} data-id="{{ data_source.id }}">
</p>
{% endfor %}
</div>
</div> </div>
</div> {% else %}
{% else %} <img src="{{ app.icon }}" height="64px">
<img src="{{ app.icon }}" height="64px"> {% endif %}
{% endif %} </div>
</a>
<div class="card-action center-align scrollbar pr-0" style="max-height: 60px; min-height: 60px; scrollbar-width: none;">
<span class="app-name-{{ app.id }} font-weight-900 card-title theme-primary-text searchable" style="font-size: 1.2rem">{{ app.name }}
<i class="material-icons activator right theme-secondary-text">more_vert</i>
{% if app.data_sources.count() > 0 %}
<i class="material-icons pointer right theme-secondary-text refresh-data-source-btn">refresh</i>
{% endif %}
</span>
<style>
.app-name-{{ app.id }} {
margin-top: 35px;
}
</style>
</div> </div>
<div class="card-action center-align scrollbar" style="max-height: 127px; min-height: 127px; scrollbar-width: none;"> <div class="card-reveal">
<h5 class="app-name-{{ app.id }}">{{ app.name }} <span class="card-title">{{ app.name }}<i class="material-icons right">close</i></span>
</h5>
{% if app.description %} {% if app.description %}
<span class="theme-secondary-text app-description">{{ app.description }}</span> <p class="theme-secondary-text app-description searchable">{{ app.description|safe }}</p>
{% else %}
<style>
.app-name-{{ app.id }} {
margin-top: 35px;
}
</style>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</a>
{# </a> This closes AppAnchor() #}
{% endmacro %} {% endmacro %}
{% macro ListViewApp(app) %} {% macro Collection(app) %}
{{ AppAnchor(app, classes="collection-item") }} <div class="col s12 m6 l4 xl3 app-card">
<div class="row"> <div class="card theme-surface-transparent scrollbar" style="max-height: 146px; min-height: 146px;">
<div class="col s12 <div class="card-content">
{% if app.data_sources.count() > 0 %} <span class="font-weight-900 card-title theme-primary-text">
l6 {% if app.icon %}
{% else %} <i class="material-icons-outlined right">{{app.icon}}</i>
l12 {% endif %}
{% endif %}"> <text class="searchable">{{ app.name }}</text>
<img src="{{ app.icon }}" class="app-icon"> </span>
<span class="theme-muted-text app-name">{{ app.name }}</span> <div class="collection">
{% if app.description %} {% for url in app.urls_json %}
<i class="material-icons-outlined tooltipped" data-position="top" data-tooltip="{{ app.description }}">info</i>
{% endif %} <div class="collection-item">
</div> {% if url['icon'] %}
<div class="col s12 l6 right-align"> <img src="{{ url['icon'] }}" height="24px" class="mr-2" style="position: relative; top: 5px;">
{% if app.data_sources.count() > 0 %} {% endif %}
<span class="data-source-loading">{{ preload_circle() }}</span> {% if url['open_in'] == 'this_tab' %}
{% for data_source in app.data_sources %} <a href="{{ url['url'] }}" class="font-weight-700 searchable" style="font-size: 1.1rem">
<span class="data-source-container theme-primary-text" {% else %}
data-url="{{ url_for('main.load_data_source') }}" <a href="{{ url['url'] }}" target="_blank" class="font-weight-700 searchable" style="font-size: 1.1rem">
data-id="{{ data_source.id }}"> {% endif %}
</span> {{ url['name'] }}
{% endfor %} </a>
{% endif %} </div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endmacro %}
{% macro Custom(app) %}
<div class="col s12 m6 l4 xl3 app-card">
<div class="card theme-surface-transparent scrollbar" style="max-height: 146px; min-height: 146px;">
<div class="hide searchable">{{ app.name }}</div>
<div class="card-content">
<div class="col s12">
<div class="progress data-source-loading">
<div class="indeterminate"></div>
</div>
{% for data_source in app.data_sources %}
<p class="data-source-container"
data-url="{{ url_for('main.load_data_source') }}"
data-id="{{ data_source.id }}">
</p>
{% endfor %}
</div>
</div>
</div> </div>
</div> </div>
</a>
{# </a> This closes AppAnchor() #}
{% endmacro %} {% endmacro %}

View File

@ -63,18 +63,22 @@
<div class="row"> <div class="row">
<div class="col s12 mb-2"> <div class="col s12 mb-2">
<ul class="tabs tabs-fixed-width"> <ul class="tabs tabs-fixed-width">
<li class="tab col s3"><a href="#config-readme"> <li class="tab col s3"><a href="#settings-readme">
<i class="material-icons-outlined">info</i> <i class="material-icons-outlined">settings</i>
</a></li>
<li class="tab col s3"><a href="#cards-readme">
<i class="material-icons-outlined">aspect_ratio</i>
</a></li>
<li class="tab col s3"><a href="#data-sources-readme">
<i class="material-icons-outlined">language</i>
</a></li> </a></li>
<li class="tab col s3"><a href="#images"> <li class="tab col s3"><a href="#images">
<i class="material-icons-outlined">photo_library</i> <i class="material-icons-outlined">photo_library</i>
</a></li> </a></li>
<li class="tab col s3"><a href="#apps">
<i class="material-icons-outlined">apps</i>
</a></li>
<li class="tab col s3"><a href="#about"> <li class="tab col s3"><a href="#about">
<i class="material-icons-outlined">security</i> <i class="material-icons-outlined">security</i>
</a></li> </a></li>
@ -85,8 +89,14 @@
{{ FilesTab() }} {{ FilesTab() }}
</div> </div>
<div id="apps" class="col s12 scrollbar settings-page-card-right"> <div id="settings-readme" class="col s12 scrollbar settings-page-card-right scrollbar-x">
<h5>App Templates</h5> {{ config_readme['settings']|safe }}
</div>
<div id="cards-readme" class="col s12 scrollbar settings-page-card-right scrollbar-x">
<h4>Cards</h4>
<h5>Card Templates</h5>
<span>Search for preconfigured cards included with DashMachine.</span>
<div class="row card-filter-container"> <div class="row card-filter-container">
<div class="col s12 input-field"> <div class="col s12 input-field">
<span> <span>
@ -105,10 +115,11 @@
</div> </div>
</div> </div>
{{ config_readme['cards']|safe }}
</div> </div>
<div id="config-readme" class="col s12 scrollbar settings-page-card-right scrollbar-x"> <div id="data-sources-readme" class="col s12 scrollbar settings-page-card-right scrollbar-x">
{{ config_readme|safe }} {{ config_readme['data_sources']|safe }}
</div> </div>
<div id="about" class="col s12 scrollbar settings-page-card-right"> <div id="about" class="col s12 scrollbar settings-page-card-right">

View File

@ -15,5 +15,5 @@ class User(db.Model, UserMixin):
theme = db.Column(db.String()) theme = db.Column(db.String())
background = db.Column(db.String()) background = db.Column(db.String())
accent = db.Column(db.String()) accent = db.Column(db.String())
home_view_mode = db.Column(db.String(), default="grid")
sidebar_default = db.Column(db.String()) sidebar_default = db.Column(db.String())
tags_expanded = db.Column(db.String())

View File

@ -5,7 +5,6 @@ background = None
roles = admin,user,public_user roles = admin,user,public_user
home_access_groups = admin_only home_access_groups = admin_only
settings_access_groups = admin_only settings_access_groups = admin_only
home_view_mode = grid
custom_app_title = DashMachine custom_app_title = DashMachine
sidebar_default = open sidebar_default = open

61
readme_cards.md Normal file
View File

@ -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 |

6
readme_data_sources.md Normal file
View File

@ -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`

95
readme_settings.md Normal file
View File

@ -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
>```
>Its 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.