diff --git a/dashmachine/main/read_config.py b/dashmachine/main/read_config.py new file mode 100644 index 0000000..af5cbbc --- /dev/null +++ b/dashmachine/main/read_config.py @@ -0,0 +1,180 @@ +import os +from configparser import ConfigParser +from dashmachine.main.models import Apps, ApiCalls, Groups +from dashmachine.settings_system.models import Settings +from dashmachine.paths import user_data_folder +from dashmachine import db + + +def row2dict(row): + d = {} + for column in row.__table__.columns: + d[column.name] = str(getattr(row, column.name)) + + return d + + +def read_config(): + config = ConfigParser() + try: + config.read(os.path.join(user_data_folder, "config.ini")) + except Exception as e: + return {"msg": f"Invalid Config: {e}."} + + Apps.query.delete() + ApiCalls.query.delete() + Settings.query.delete() + Groups.query.delete() + + for section in config.sections(): + + # Settings creation + if section == "Settings": + settings = Settings() + if "theme" in config["Settings"]: + settings.theme = config["Settings"]["theme"] + else: + settings.theme = "light" + + if "accent" in config["Settings"]: + settings.accent = config["Settings"]["accent"] + else: + settings.accent = "orange" + + if "background" in config["Settings"]: + settings.background = config["Settings"]["background"] + else: + settings.background = "None" + + if "roles" in config["Settings"]: + settings.roles = config["Settings"]["roles"] + if "admin" not in settings.roles: + settings.roles += ",admin" + if "user" not in settings.roles: + settings.roles += ",user" + if "public_user" not in settings.roles: + settings.roles += ",public_user" + else: + settings.roles = "admin,user,public_user" + + if "home_access_groups" in config["Settings"]: + settings.home_access_groups = config["Settings"]["home_access_groups"] + else: + settings.home_access_groups = "admin_only" + + if "settings_access_groups" in config["Settings"]: + settings.settings_access_groups = config["Settings"][ + "settings_access_groups" + ] + else: + settings.settings_access_groups = "admin_only" + + db.session.add(settings) + db.session.commit() + + # Groups creation + elif "roles" in config[section]: + group = Groups() + group.name = section + group.roles = config[section]["roles"] + db.session.add(group) + db.session.commit() + + # API call creation + elif "platform" in config[section]: + api_call = ApiCalls() + api_call.name = section + if "resource" in config[section]: + api_call.resource = config[section]["resource"] + else: + return {"msg": f"Invalid Config: {section} does not contain resource."} + + if "method" in config[section]: + api_call.method = config[section]["method"] + else: + api_call.method = "GET" + + if "payload" in config[section]: + api_call.payload = config[section]["payload"] + else: + api_call.payload = None + + if "authentication" in config[section]: + api_call.authentication = config[section]["authentication"] + else: + api_call.authentication = None + + if "username" in config[section]: + api_call.username = config[section]["username"] + else: + api_call.username = None + + if "password" in config[section]: + api_call.password = config[section]["password"] + else: + api_call.password = None + + if "value_template" in config[section]: + api_call.value_template = config[section]["value_template"] + else: + api_call.value_template = section + + db.session.add(api_call) + db.session.commit() + continue + + else: + # App creation + app = Apps() + app.name = section + if "prefix" in config[section]: + app.prefix = config[section]["prefix"] + else: + return {"msg": f"Invalid Config: {section} does not contain prefix."} + + if "url" in config[section]: + app.url = config[section]["url"] + else: + return {"msg": f"Invalid Config: {section} does not contain url."} + + if "icon" in config[section]: + app.icon = config[section]["icon"] + else: + app.icon = None + + if "sidebar_icon" in config[section]: + app.sidebar_icon = config[section]["sidebar_icon"] + else: + app.sidebar_icon = app.icon + + if "description" in config[section]: + app.description = config[section]["description"] + else: + app.description = None + + if "open_in" in config[section]: + app.open_in = config[section]["open_in"] + else: + app.open_in = "this_tab" + + if "data_template" in config[section]: + app.data_template = config[section]["data_template"] + else: + app.data_template = None + + if "groups" in config[section]: + app.groups = config[section]["groups"] + else: + app.groups = None + + db.session.add(app) + db.session.commit() + + group = Groups.query.filter_by(name="admin_only").first() + if not group: + group = Groups() + group.name = "admin_only" + group.roles = "admin" + db.session.add(group) + db.session.commit() + return {"msg": "success", "settings": row2dict(settings)} diff --git a/dashmachine/main/routes.py b/dashmachine/main/routes.py index 32a3952..7e95273 100755 --- a/dashmachine/main/routes.py +++ b/dashmachine/main/routes.py @@ -5,7 +5,8 @@ from htmlmin.main import minify from flask import render_template, url_for, redirect, request, Blueprint, jsonify from flask_login import current_user from dashmachine.main.models import Files -from dashmachine.main.utils import get_rest_data +from dashmachine.main.utils import get_rest_data, public_route, check_groups +from dashmachine.settings_system.models import Settings from dashmachine.paths import cache_folder from dashmachine import app, db @@ -49,9 +50,13 @@ def check_valid_login(): # ------------------------------------------------------------------------------ # /home # ------------------------------------------------------------------------------ +@public_route @main.route("/") @main.route("/home", methods=["GET", "POST"]) def home(): + settings = Settings.query.first() + if not check_groups(settings.home_access_groups, current_user): + return redirect(url_for("user_system.login")) return render_template("main/home.html") diff --git a/dashmachine/main/utils.py b/dashmachine/main/utils.py index b9b6706..6cb2a45 100755 --- a/dashmachine/main/utils.py +++ b/dashmachine/main/utils.py @@ -4,7 +4,8 @@ from shutil import copyfile from requests import get from configparser import ConfigParser from dashmachine.paths import dashmachine_folder, images_folder, root_folder -from dashmachine.main.models import Apps, ApiCalls, TemplateApps, Groups +from dashmachine.main.models import ApiCalls, TemplateApps, Groups +from dashmachine.main.read_config import read_config from dashmachine.settings_system.models import Settings from dashmachine.user_system.models import User from dashmachine.user_system.utils import add_edit_user @@ -19,158 +20,6 @@ def row2dict(row): return d -def read_config(): - config = ConfigParser() - try: - config.read("dashmachine/user_data/config.ini") - except Exception as e: - return {"msg": f"Invalid Config: {e}."} - - Apps.query.delete() - ApiCalls.query.delete() - Settings.query.delete() - Groups.query.delete() - - for section in config.sections(): - - # Settings creation - if section == "Settings": - settings = Settings() - if "theme" in config["Settings"]: - settings.theme = config["Settings"]["theme"] - else: - settings.theme = "light" - - if "accent" in config["Settings"]: - settings.accent = config["Settings"]["accent"] - else: - settings.accent = "orange" - - if "background" in config["Settings"]: - settings.background = config["Settings"]["background"] - else: - settings.background = "None" - - if "roles" in config["Settings"]: - settings.roles = config["Settings"]["roles"] - else: - settings.roles = "admin" - - if "home_access_groups" in config["Settings"]: - settings.home_access_groups = config["Settings"]["home_access_groups"] - else: - settings.home_access_groups = "admin_only" - - if "settings_access_groups" in config["Settings"]: - settings.settings_access_groups = config["Settings"][ - "settings_access_groups" - ] - else: - settings.settings_access_groups = "admin_only" - - db.session.add(settings) - db.session.commit() - - # Groups creation - elif "roles" in config[section]: - group = Groups() - group.name = section - group.roles = config[section]["roles"] - db.session.add(group) - db.session.commit() - - # API call creation - elif "platform" in config[section]: - api_call = ApiCalls() - api_call.name = section - if "resource" in config[section]: - api_call.resource = config[section]["resource"] - else: - return {"msg": f"Invalid Config: {section} does not contain resource."} - - if "method" in config[section]: - api_call.method = config[section]["method"] - else: - api_call.method = "GET" - - if "payload" in config[section]: - api_call.payload = config[section]["payload"] - else: - api_call.payload = None - - if "authentication" in config[section]: - api_call.authentication = config[section]["authentication"] - else: - api_call.authentication = None - - if "username" in config[section]: - api_call.username = config[section]["username"] - else: - api_call.username = None - - if "password" in config[section]: - api_call.password = config[section]["password"] - else: - api_call.password = None - - if "value_template" in config[section]: - api_call.value_template = config[section]["value_template"] - else: - api_call.value_template = section - - db.session.add(api_call) - db.session.commit() - continue - - else: - # App creation - app = Apps() - app.name = section - if "prefix" in config[section]: - app.prefix = config[section]["prefix"] - else: - return {"msg": f"Invalid Config: {section} does not contain prefix."} - - if "url" in config[section]: - app.url = config[section]["url"] - else: - return {"msg": f"Invalid Config: {section} does not contain url."} - - if "icon" in config[section]: - app.icon = config[section]["icon"] - else: - app.icon = None - - if "sidebar_icon" in config[section]: - app.sidebar_icon = config[section]["sidebar_icon"] - else: - app.sidebar_icon = app.icon - - if "description" in config[section]: - app.description = config[section]["description"] - else: - app.description = None - - if "open_in" in config[section]: - app.open_in = config[section]["open_in"] - else: - app.open_in = "this_tab" - - if "data_template" in config[section]: - app.data_template = config[section]["data_template"] - else: - app.data_template = None - - if "groups" in config[section]: - app.groups = config[section]["groups"] - else: - app.groups = None - - db.session.add(app) - db.session.commit() - return {"msg": "success", "settings": row2dict(settings)} - - def read_template_apps(): config = ConfigParser() try: @@ -248,6 +97,11 @@ def dashmachine_init(): role=settings.roles.split(",")[0].strip(), ) + users = User.query.all() + for user in users: + if not user.role: + user.role = "admin" + def get_rest_data(template): while template and template.find("{{") > -1: @@ -267,3 +121,25 @@ def do_api_call(key): exec(f"{key} = {value.json()}") value = str(eval(api_call.value_template)) return value + + +def check_groups(groups, current_user): + if current_user.is_anonymous: + current_user.role = "public_user" + + if groups: + groups_list = groups.split(",") + roles_list = [] + for group in groups_list: + group = Groups.query.filter_by(name=group.strip()).first() + for group_role in group.roles.split(","): + roles_list.append(group_role.strip()) + if current_user.role in roles_list: + return True + else: + return False + else: + if current_user.role == "admin": + return True + else: + return False diff --git a/dashmachine/paths.py b/dashmachine/paths.py index 7397a4f..d5e6bb0 100755 --- a/dashmachine/paths.py +++ b/dashmachine/paths.py @@ -13,6 +13,8 @@ root_folder = get_root_folder() dashmachine_folder = os.path.join(root_folder, "dashmachine") +user_data_folder = os.path.join(dashmachine_folder, "user_data") + static_folder = os.path.join(dashmachine_folder, "static") images_folder = os.path.join(static_folder, "images") diff --git a/dashmachine/rest_api/resources.py b/dashmachine/rest_api/resources.py index a18f68a..d24173b 100755 --- a/dashmachine/rest_api/resources.py +++ b/dashmachine/rest_api/resources.py @@ -1,5 +1,3 @@ -import os -from flask import request from flask_restful import Resource from dashmachine.version import version @@ -7,15 +5,3 @@ from dashmachine.version import version class GetVersion(Resource): def get(self): return {"Version": version} - - -class ServerShutdown(Resource): - def get(self): - os.system("shutdown now") - return {"Done"} - - -class ServerReboot(Resource): - def get(self): - os.system("reboot") - return {"Done"} diff --git a/dashmachine/settings_system/routes.py b/dashmachine/settings_system/routes.py index d447ce7..9000053 100644 --- a/dashmachine/settings_system/routes.py +++ b/dashmachine/settings_system/routes.py @@ -1,23 +1,35 @@ import os from shutil import move -from flask import render_template, request, Blueprint, jsonify -from dashmachine.settings_system.forms import ConfigForm +from flask_login import current_user +from flask import render_template, request, Blueprint, jsonify, redirect, url_for from dashmachine.user_system.forms import UserForm from dashmachine.user_system.utils import add_edit_user -from dashmachine.main.utils import read_config, row2dict +from dashmachine.main.utils import row2dict, public_route, check_groups +from dashmachine.main.read_config import read_config from dashmachine.main.models import Files, TemplateApps -from dashmachine.paths import backgrounds_images_folder, icons_images_folder -from dashmachine.version import version +from dashmachine.settings_system.forms import ConfigForm from dashmachine.settings_system.utils import load_files_html +from dashmachine.settings_system.models import Settings +from dashmachine.paths import ( + backgrounds_images_folder, + icons_images_folder, + user_data_folder, +) +from dashmachine.version import version settings_system = Blueprint("settings_system", __name__) +@public_route @settings_system.route("/settings", methods=["GET"]) def settings(): + settings_db = Settings.query.first() + if not check_groups(settings_db.settings_access_groups, current_user): + return redirect(url_for("main.home")) + config_form = ConfigForm() user_form = UserForm() - with open("dashmachine/user_data/config.ini", "r") as config_file: + with open(os.path.join(user_data_folder, "config.ini"), "r") as config_file: config_form.config.data = config_file.read() files_html = load_files_html() template_apps = [] @@ -36,7 +48,7 @@ def settings(): @settings_system.route("/settings/save_config", methods=["POST"]) def save_config(): - with open("dashmachine/user_data/config.ini", "w") as config_file: + with open(os.path.join(user_data_folder, "config.ini"), "w") as config_file: config_file.write(request.form.get("config")) msg = read_config() return jsonify(data=msg) diff --git a/dashmachine/sources.py b/dashmachine/sources.py index a470d8f..e521fa1 100644 --- a/dashmachine/sources.py +++ b/dashmachine/sources.py @@ -1,8 +1,10 @@ import os 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.utils import check_groups from dashmachine.settings_system.models import Settings from dashmachine.paths import static_folder, backgrounds_images_folder from dashmachine.cssmin import cssmin @@ -72,7 +74,14 @@ def process_css_sources(process_bundle=None, src=None, app_global=False): @app.context_processor def context_processor(): - apps = Apps.query.all() + apps = [] + apps_db = Apps.query.all() + for app_db in apps_db: + if not app_db.groups: + app_db.groups = None + if check_groups(app_db.groups, current_user): + apps.append(app_db) + settings = Settings.query.first() if settings.background == "random": settings.background = ( diff --git a/dashmachine/templates/settings_system/config-readme.html b/dashmachine/templates/settings_system/config-readme.html new file mode 100644 index 0000000..04f431d --- /dev/null +++ b/dashmachine/templates/settings_system/config-readme.html @@ -0,0 +1,279 @@ +{% macro ConfigReadme() %} +
+

Config.ini Readme + close +

+
+
+
Settings
+ + [Settings]
+ theme = dark
+ accent = orange
+ background = static/images/backgrounds/background.png
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableRequiredDescriptionOptions
[Settings]YesConfig section name.string
themeYesUI themelight, dark
accentYesUI accent color + orange, red, pink, purple, deepPurple, indigo, blue, lightBlue, + cyan, teal, green, lightGreen, lime, yellow, amber, deepOrange, brown, grey, blueGrey +
backgroundYesBackground image for the UI/static/images/backgrounds/yourpicture.png, external link to image, None, random
rolesNoUser roles for access groups.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_groupsNoDefine which access groups can access the /home pageRoles defined in your config. If not defined, default is admin_only
settings_access_groupsNoDefine which access groups can access the /settings pageRoles defined in your config. If not defined, default is admin_only
+ +
Apps
+ + [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_template = None +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableRequiredDescriptionOptions
[App Name]YesThe name of your app.string
prefixYesThe prefix for the app's url.web prefix, e.g. http:// or https://
urlYesThe url for your app.web url, e.g. myapp.com
iconNoIcon for the dashboard./static/images/icons/yourpicture.png, external link to image
sidebar_iconNoIcon for the sidenav./static/images/icons/yourpicture.png, external link to image
descriptionNoA short description for the app.string
open_inYesopen the app in the current tab, an iframe or a new tabiframe, new_tab, this_tab
data_templateNoTemplate for displaying variable(s) from rest data *Note: you must have a rest data variable set up in the configexample: Data: {{ '{{ your_variable }}' }}
+ +
Access Groups
+ + + [public]
+ roles = admin, user, public_user
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
VariableRequiredDescriptionOptions
[Group Name]YesName for access groupstring
rolesYesA comma separated list of user roles allowed to view apps in this access groupRoles defined in your config. If not defined, defaults are admin and public_user
+ +
Note:
+ + if no access groups are defined in the config, the application will create a default group called 'admin_only' with 'roles = admin' + + +
Api Data
+ + [variable_name]
+ platform = rest
+ resource = your-website.com
+ value_template = variable_name
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableRequiredDescriptionOptions
[variable_name]YesThe variable to be made available to apps.variable (python syntax)
platformYesPlatform for data sourcerest
resourceYesThe url for the api call.myapp.com/api/hello
value_templateNoTranform the data returned by the api call (python syntax)variable_name[0]['info']
methodNOT IMPLEMENTEDNOT IMPLEMENTEDNOT IMPLEMENTED
payloadNOT IMPLEMENTEDNOT IMPLEMENTEDNOT IMPLEMENTED
authenticationNOT IMPLEMENTEDNOT IMPLEMENTEDNOT IMPLEMENTED
usernameNOT IMPLEMENTEDNOT IMPLEMENTEDNOT IMPLEMENTED
passwordNOT IMPLEMENTEDNOT IMPLEMENTEDNOT IMPLEMENTED
+ +
Api Data Example
+

Say we wanted to display how many Pokemon there are using the PokeAPI, we would add the following to the config:

+ + [num_pokemon]
+ platform = rest
+ resource = https://pokeapi.co/api/v2/pokemon
+ value_template = num_pokemon['count']
+
+ +

Then in the config entry for the app you want to add this to, you would add:

+ + + data_template = Pokemon: {{ '{{ num_pokemon }}' }} + + +
+{% endmacro %} \ No newline at end of file diff --git a/dashmachine/templates/settings_system/settings.html b/dashmachine/templates/settings_system/settings.html index e3fda11..01e9fd4 100644 --- a/dashmachine/templates/settings_system/settings.html +++ b/dashmachine/templates/settings_system/settings.html @@ -1,6 +1,7 @@ {% extends "main/layout.html" %} {% from 'global_macros.html' import input, button %} {% from 'main/tcdrop.html' import tcdrop %} +{% from 'settings_system/config-readme.html' import ConfigReadme %} {% block page_vendor_css %} {% endblock page_vendor_css %} @@ -22,228 +23,7 @@ diff --git a/dashmachine/user_system/routes.py b/dashmachine/user_system/routes.py index 5437d85..6ee494a 100755 --- a/dashmachine/user_system/routes.py +++ b/dashmachine/user_system/routes.py @@ -18,9 +18,6 @@ user_system = Blueprint("user_system", __name__) def login(): user = User.query.first() - if current_user.is_authenticated: - return redirect(url_for("main.home")) - form = UserForm() if form.validate_on_submit(): diff --git a/dashmachine/user_system/utils.py b/dashmachine/user_system/utils.py index 6b6ef0c..e01bf9e 100755 --- a/dashmachine/user_system/utils.py +++ b/dashmachine/user_system/utils.py @@ -2,9 +2,11 @@ from dashmachine import db, bcrypt from dashmachine.user_system.models import User -def add_edit_user(username, password, user_id=None, role=None): +def add_edit_user(username, password, user_id=None, role=None, new=False): if user_id: user = User.query.filter_by(id=user_id).first() + elif new: + user = User() else: user = User.query.first() if not user: diff --git a/default_config.ini b/default_config.ini index 172cb3d..c768f39 100644 --- a/default_config.ini +++ b/default_config.ini @@ -1,31 +1,4 @@ -# -------- -# SETTINGS -# -------- [Settings] theme = light accent = orange -background = None -roles = admin, user, public_user -home_access_groups = admin_only -settings_access_groups = admin_only - -# ------------- -# ACCESS GROUPS -# ------------- -[public] -roles = admin, user, public_user - -[private] -roles = admin, user - -[admin_only] -roles = admin - -# -------- -# API DATA -# -------- - - -# ---- -# APPS -# ---- \ No newline at end of file +background = None \ No newline at end of file