Merge branch 'develop'
This commit is contained in:
commit
f3cf929cc5
15
README.md
15
README.md
@ -16,7 +16,9 @@
|
|||||||
* hideable sidebar with dragable reveal button
|
* hideable sidebar with dragable reveal button
|
||||||
* user login system
|
* user login system
|
||||||
* 'app templates' which are sample config entries for popular self hosted apps
|
* 'app templates' which are sample config entries for popular self hosted apps
|
||||||
* ability to display rest api data on application cards
|
* powerful plugin system for adding data from various sources to display on cards
|
||||||
|
* multiple users, access groups, access settings
|
||||||
|
* tagging system
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
### Docker
|
### Docker
|
||||||
@ -59,14 +61,3 @@ If you change the config.ini file, you either have to restart the container (or
|
|||||||
* Materialize css
|
* Materialize css
|
||||||
* JavaScript/jQuery/jQueryUI
|
* JavaScript/jQuery/jQueryUI
|
||||||
* Requests (python)
|
* Requests (python)
|
||||||
|
|
||||||
## Version 1.0 TODO list
|
|
||||||
- [ ] finish rest api data functions (post requests, auth)
|
|
||||||
- [ ] finish rest api data display (make it look prettier)
|
|
||||||
- [ ] include nginx & gunicorn in docker container
|
|
||||||
- [ ] tag/folder system & support for services without web redirection
|
|
||||||
- [ ] add more template apps from popular self hosted apps
|
|
||||||
- [ ] pull request template for adding template apps
|
|
||||||
- [ ] rest api data examples for template apps
|
|
||||||
- [ ] find a way to mirror this repo on GitHub for exposure
|
|
||||||
- [ ] support multiple users
|
|
85
config_readme.md
Normal file
85
config_readme.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#### Config.ini Readme
|
||||||
|
|
||||||
|
##### Settings
|
||||||
|
This is the configuration entry for DashMachine's settings. DashMachine will not work if
|
||||||
|
this is missing. As for all config entries, [Settings] can only appear once in the config.
|
||||||
|
If you change the config.ini file, you either have to restart the container
|
||||||
|
(or python script) or click the ‘save’ button in the config section of settings for the
|
||||||
|
config to be applied.
|
||||||
|
```ini
|
||||||
|
[Settings]
|
||||||
|
theme = light
|
||||||
|
accent = orange
|
||||||
|
background = None
|
||||||
|
roles = admin,user,public_user
|
||||||
|
home_access_groups = admin_only
|
||||||
|
settings_access_groups = admin_only
|
||||||
|
```
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
##### 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 | No | 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. 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
|
||||||
|
[public]
|
||||||
|
roles = admin, user, 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'
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
#### 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 `plaform` variable,
|
||||||
|
which tells DashMachine which platform file in the platform folder to load. **Note:** you are able to
|
||||||
|
load your own plaform 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`
|
@ -6,9 +6,10 @@ from flask_sqlalchemy import SQLAlchemy
|
|||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from flask_restful import Api
|
from flask_restful import Api
|
||||||
|
from dashmachine.paths import user_data_folder
|
||||||
|
|
||||||
if not os.path.isdir("dashmachine/user_data"):
|
if not os.path.isdir(user_data_folder):
|
||||||
os.mkdir("dashmachine/user_data")
|
os.mkdir(user_data_folder)
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
@ -19,3 +19,8 @@ def error_403(error):
|
|||||||
@error_pages.app_errorhandler(500)
|
@error_pages.app_errorhandler(500)
|
||||||
def error_500(error):
|
def error_500(error):
|
||||||
return render_template("/error_pages/500.html"), 500
|
return render_template("/error_pages/500.html"), 500
|
||||||
|
|
||||||
|
|
||||||
|
@error_pages.route("/unauthorized")
|
||||||
|
def unauthorized():
|
||||||
|
return render_template("/error_pages/unauthorized.html")
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import SelectField
|
||||||
|
|
||||||
|
|
||||||
|
class TagsForm(FlaskForm):
|
||||||
|
tags = SelectField(choices=[("All tags", "All tags")])
|
@ -1,5 +1,11 @@
|
|||||||
from dashmachine import db
|
from dashmachine import db
|
||||||
|
|
||||||
|
rel_app_data_source = db.Table(
|
||||||
|
"rel_app_data_source",
|
||||||
|
db.Column("data_source_id", db.Integer, db.ForeignKey("data_sources.id")),
|
||||||
|
db.Column("app_id", db.Integer, db.ForeignKey("apps.id")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Files(db.Model):
|
class Files(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@ -20,6 +26,8 @@ class Apps(db.Model):
|
|||||||
description = db.Column(db.String())
|
description = db.Column(db.String())
|
||||||
open_in = db.Column(db.String())
|
open_in = db.Column(db.String())
|
||||||
data_template = db.Column(db.String())
|
data_template = db.Column(db.String())
|
||||||
|
groups = db.Column(db.String())
|
||||||
|
tags = db.Column(db.String())
|
||||||
|
|
||||||
|
|
||||||
class TemplateApps(db.Model):
|
class TemplateApps(db.Model):
|
||||||
@ -33,17 +41,31 @@ class TemplateApps(db.Model):
|
|||||||
open_in = db.Column(db.String())
|
open_in = db.Column(db.String())
|
||||||
|
|
||||||
|
|
||||||
class ApiCalls(db.Model):
|
class DataSources(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())
|
||||||
resource = db.Column(db.String())
|
platform = db.Column(db.String())
|
||||||
method = db.Column(db.String())
|
args = db.relationship("DataSourcesArgs", backref="data_source")
|
||||||
payload = db.Column(db.String())
|
apps = db.relationship(
|
||||||
authentication = db.Column(db.String())
|
"Apps",
|
||||||
username = db.Column(db.String())
|
secondary=rel_app_data_source,
|
||||||
password = db.Column(db.String())
|
backref=db.backref("data_sources", lazy="dynamic"),
|
||||||
value_template = db.Column(db.String())
|
)
|
||||||
|
|
||||||
|
|
||||||
db.create_all()
|
class DataSourcesArgs(db.Model):
|
||||||
db.session.commit()
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
key = db.Column(db.String())
|
||||||
|
value = db.Column(db.String())
|
||||||
|
data_source_id = db.Column(db.Integer, db.ForeignKey("data_sources.id"))
|
||||||
|
|
||||||
|
|
||||||
|
class Groups(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String())
|
||||||
|
roles = db.Column(db.String())
|
||||||
|
|
||||||
|
|
||||||
|
class Tags(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String())
|
||||||
|
176
dashmachine/main/read_config.py
Normal file
176
dashmachine/main/read_config.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import os
|
||||||
|
from configparser import ConfigParser
|
||||||
|
from dashmachine.main.models import Apps, Groups, DataSources, DataSourcesArgs, Tags
|
||||||
|
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}."}
|
||||||
|
|
||||||
|
ds_list = DataSources.query.all()
|
||||||
|
for ds in ds_list:
|
||||||
|
ds.apps.clear()
|
||||||
|
DataSources.query.delete()
|
||||||
|
DataSourcesArgs.query.delete()
|
||||||
|
Apps.query.delete()
|
||||||
|
Settings.query.delete()
|
||||||
|
Groups.query.delete()
|
||||||
|
Tags.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()
|
||||||
|
|
||||||
|
# Data source creation
|
||||||
|
elif "platform" in config[section]:
|
||||||
|
data_source = DataSources()
|
||||||
|
data_source.name = section
|
||||||
|
data_source.platform = config[section]["platform"]
|
||||||
|
db.session.add(data_source)
|
||||||
|
db.session.commit()
|
||||||
|
for key, value in config[section].items():
|
||||||
|
if key not in ["name", "platform"]:
|
||||||
|
arg = DataSourcesArgs()
|
||||||
|
arg.key = key
|
||||||
|
arg.value = value
|
||||||
|
arg.data_source = data_source
|
||||||
|
db.session.add(arg)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
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 "groups" in config[section]:
|
||||||
|
app.groups = config[section]["groups"]
|
||||||
|
else:
|
||||||
|
app.groups = None
|
||||||
|
|
||||||
|
if "tags" in config[section]:
|
||||||
|
app.tags = config[section]["tags"].title()
|
||||||
|
for tag in app.tags.split(","):
|
||||||
|
tag = tag.strip().title()
|
||||||
|
if not Tags.query.filter_by(name=tag).first():
|
||||||
|
tag_db = Tags(name=tag)
|
||||||
|
db.session.add(tag_db)
|
||||||
|
db.session.commit()
|
||||||
|
else:
|
||||||
|
app.tags = None
|
||||||
|
|
||||||
|
db.session.add(app)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if "data_sources" in config[section]:
|
||||||
|
for config_ds in config[section]["data_sources"].split(","):
|
||||||
|
db_ds = DataSources.query.filter_by(name=config_ds.strip()).first()
|
||||||
|
if db_ds:
|
||||||
|
app.data_sources.append(db_ds)
|
||||||
|
db.session.merge(app)
|
||||||
|
db.session.commit()
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"msg": f"Invalid Config: {section} has a data_source variable that doesn't exist."
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}
|
@ -4,8 +4,14 @@ from secrets import token_hex
|
|||||||
from htmlmin.main import minify
|
from htmlmin.main import minify
|
||||||
from flask import render_template, url_for, redirect, request, Blueprint, jsonify
|
from flask import render_template, url_for, redirect, request, Blueprint, jsonify
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from dashmachine.main.models import Files
|
from dashmachine.main.models import Files, Apps, DataSources, Tags
|
||||||
from dashmachine.main.utils import get_rest_data
|
from dashmachine.main.forms import TagsForm
|
||||||
|
from dashmachine.main.utils import (
|
||||||
|
public_route,
|
||||||
|
check_groups,
|
||||||
|
get_data_source,
|
||||||
|
)
|
||||||
|
from dashmachine.settings_system.models import Settings
|
||||||
from dashmachine.paths import cache_folder
|
from dashmachine.paths import cache_folder
|
||||||
from dashmachine import app, db
|
from dashmachine import app, db
|
||||||
|
|
||||||
@ -49,21 +55,35 @@ def check_valid_login():
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# /home
|
# /home
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@public_route
|
||||||
@main.route("/")
|
@main.route("/")
|
||||||
@main.route("/home", methods=["GET", "POST"])
|
@main.route("/home", methods=["GET", "POST"])
|
||||||
def home():
|
def home():
|
||||||
return render_template("main/home.html")
|
tags_form = TagsForm()
|
||||||
|
tags_form.tags.choices += [
|
||||||
|
(tag.name, tag.name) for tag in Tags.query.order_by(Tags.name).all()
|
||||||
|
]
|
||||||
|
settings = Settings.query.first()
|
||||||
|
if not check_groups(settings.home_access_groups, current_user):
|
||||||
|
return redirect(url_for("error_pages.unauthorized"))
|
||||||
|
return render_template("main/home.html", tags_form=tags_form)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/app_view?<url>", methods=["GET"])
|
@public_route
|
||||||
def app_view(url):
|
@main.route("/app_view?<app_id>", methods=["GET"])
|
||||||
return render_template("main/app-view.html", url=f"https://{url}")
|
def app_view(app_id):
|
||||||
|
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}")
|
||||||
|
|
||||||
|
|
||||||
@main.route("/load_rest_data", methods=["GET"])
|
@main.route("/load_data_source", methods=["GET"])
|
||||||
def load_rest_data():
|
def load_data_source():
|
||||||
data_template = get_rest_data(request.args.get("template"))
|
data_source = DataSources.query.filter_by(id=request.args.get("id")).first()
|
||||||
return data_template
|
data = get_data_source(data_source)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import importlib
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from requests import get
|
from requests import get
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from dashmachine.paths import dashmachine_folder, images_folder, root_folder
|
from dashmachine.paths import dashmachine_folder, images_folder, root_folder
|
||||||
from dashmachine.main.models import Apps, ApiCalls, TemplateApps
|
from dashmachine.main.models import TemplateApps, Groups
|
||||||
|
from dashmachine.main.read_config import read_config
|
||||||
from dashmachine.settings_system.models import Settings
|
from dashmachine.settings_system.models import Settings
|
||||||
from dashmachine.user_system.models import User
|
from dashmachine.user_system.models import User
|
||||||
from dashmachine.user_system.utils import add_edit_user
|
from dashmachine.user_system.utils import add_edit_user
|
||||||
@ -19,119 +21,6 @@ def row2dict(row):
|
|||||||
return d
|
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()
|
|
||||||
|
|
||||||
try:
|
|
||||||
settings = Settings(
|
|
||||||
theme=config["Settings"]["theme"],
|
|
||||||
accent=config["Settings"]["accent"],
|
|
||||||
background=config["Settings"]["background"],
|
|
||||||
)
|
|
||||||
db.session.add(settings)
|
|
||||||
db.session.commit()
|
|
||||||
except Exception as e:
|
|
||||||
return {"msg": f"Invalid Config: {e}."}
|
|
||||||
|
|
||||||
for section in config.sections():
|
|
||||||
if section != "Settings":
|
|
||||||
|
|
||||||
# API call creation
|
|
||||||
if "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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
db.session.add(app)
|
|
||||||
db.session.commit()
|
|
||||||
return {"msg": "success", "settings": row2dict(settings)}
|
|
||||||
|
|
||||||
|
|
||||||
def read_template_apps():
|
def read_template_apps():
|
||||||
config = ConfigParser()
|
config = ConfigParser()
|
||||||
try:
|
try:
|
||||||
@ -165,13 +54,17 @@ def public_route(decorated_function):
|
|||||||
|
|
||||||
|
|
||||||
def dashmachine_init():
|
def dashmachine_init():
|
||||||
|
db.create_all()
|
||||||
|
db.session.commit()
|
||||||
|
migrate_cmd = "python " + os.path.join(root_folder, "manage_db.py db stamp head")
|
||||||
|
subprocess.run(migrate_cmd, stderr=subprocess.PIPE, shell=True, encoding="utf-8")
|
||||||
|
|
||||||
migrate_cmd = "python " + os.path.join(root_folder, "manage_db.py db migrate")
|
migrate_cmd = "python " + os.path.join(root_folder, "manage_db.py db migrate")
|
||||||
subprocess.run(migrate_cmd, stderr=subprocess.PIPE, shell=True, encoding="utf-8")
|
subprocess.run(migrate_cmd, stderr=subprocess.PIPE, shell=True, encoding="utf-8")
|
||||||
|
|
||||||
upgrade_cmd = "python " + os.path.join(root_folder, "manage_db.py db upgrade")
|
upgrade_cmd = "python " + os.path.join(root_folder, "manage_db.py db upgrade")
|
||||||
subprocess.run(upgrade_cmd, stderr=subprocess.PIPE, shell=True, encoding="utf-8")
|
subprocess.run(upgrade_cmd, stderr=subprocess.PIPE, shell=True, encoding="utf-8")
|
||||||
|
|
||||||
read_config()
|
|
||||||
read_template_apps()
|
read_template_apps()
|
||||||
user_data_folder = os.path.join(dashmachine_folder, "user_data")
|
user_data_folder = os.path.join(dashmachine_folder, "user_data")
|
||||||
|
|
||||||
@ -193,28 +86,54 @@ def dashmachine_init():
|
|||||||
config_file = os.path.join(user_data_folder, "config.ini")
|
config_file = os.path.join(user_data_folder, "config.ini")
|
||||||
if not os.path.exists(config_file):
|
if not os.path.exists(config_file):
|
||||||
copyfile("default_config.ini", config_file)
|
copyfile("default_config.ini", config_file)
|
||||||
|
|
||||||
read_config()
|
read_config()
|
||||||
|
|
||||||
user = User.query.first()
|
user = User.query.first()
|
||||||
if not user:
|
if not user:
|
||||||
add_edit_user(username="admin", password="adminadmin")
|
settings = Settings.query.first()
|
||||||
|
add_edit_user(
|
||||||
|
username="admin",
|
||||||
|
password="adminadmin",
|
||||||
|
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):
|
def check_groups(groups, current_user):
|
||||||
while template and template.find("{{") > -1:
|
if current_user.is_anonymous:
|
||||||
start_braces = template.find("{{") + 2
|
current_user.role = "public_user"
|
||||||
end_braces = template.find("}}")
|
|
||||||
key = template[start_braces:end_braces].strip()
|
if groups:
|
||||||
key_w_braces = template[start_braces - 2 : end_braces + 2]
|
groups_list = groups.split(",")
|
||||||
value = do_api_call(key)
|
roles_list = []
|
||||||
template = template.replace(key_w_braces, value)
|
for group in groups_list:
|
||||||
return template
|
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
|
||||||
|
|
||||||
|
|
||||||
def do_api_call(key):
|
def get_data_source(data_source):
|
||||||
api_call = ApiCalls.query.filter_by(name=key).first()
|
data_source_args = {}
|
||||||
if api_call.method.upper() == "GET":
|
for arg in data_source.args:
|
||||||
value = get(api_call.resource)
|
arg = row2dict(arg)
|
||||||
exec(f"{key} = {value.json()}")
|
data_source_args[arg.get("key")] = arg.get("value")
|
||||||
value = str(eval(api_call.value_template))
|
data_source = row2dict(data_source)
|
||||||
return value
|
module = importlib.import_module(
|
||||||
|
f"dashmachine.platform.{data_source['platform']}", "."
|
||||||
|
)
|
||||||
|
platform = module.Platform(data_source, **data_source_args)
|
||||||
|
return platform.process()
|
||||||
|
@ -13,6 +13,10 @@ root_folder = get_root_folder()
|
|||||||
|
|
||||||
dashmachine_folder = os.path.join(root_folder, "dashmachine")
|
dashmachine_folder = os.path.join(root_folder, "dashmachine")
|
||||||
|
|
||||||
|
platform_folder = os.path.join(dashmachine_folder, "platform")
|
||||||
|
|
||||||
|
user_data_folder = os.path.join(dashmachine_folder, "user_data")
|
||||||
|
|
||||||
static_folder = os.path.join(dashmachine_folder, "static")
|
static_folder = os.path.join(dashmachine_folder, "static")
|
||||||
|
|
||||||
images_folder = os.path.join(static_folder, "images")
|
images_folder = os.path.join(static_folder, "images")
|
||||||
|
0
dashmachine/platform/__init__.py
Normal file
0
dashmachine/platform/__init__.py
Normal file
202
dashmachine/platform/pihole.py
Normal file
202
dashmachine/platform/pihole.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
from flask import render_template_string
|
||||||
|
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
def inApiLink(ip, endpoint):
|
||||||
|
return "http://" + str(ip) + "/admin/scripts/pi-hole/php/" + str(endpoint) + ".php"
|
||||||
|
|
||||||
|
|
||||||
|
class Auth(object):
|
||||||
|
def __init__(self, password):
|
||||||
|
# PiHole's web token is just a double sha256 hash of the utf8 encoded password
|
||||||
|
self.token = hashlib.sha256(
|
||||||
|
hashlib.sha256(str(password).encode()).hexdigest().encode()
|
||||||
|
).hexdigest()
|
||||||
|
self.auth_timestamp = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
class PiHole(object):
|
||||||
|
# Takes in an ip address of a pihole server
|
||||||
|
def __init__(self, ip_address):
|
||||||
|
self.ip_address = ip_address
|
||||||
|
self.auth_data = None
|
||||||
|
self.refresh()
|
||||||
|
self.pw = None
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
rawdata = requests.get(
|
||||||
|
"http://" + self.ip_address + "/admin/api.php?summary"
|
||||||
|
).json()
|
||||||
|
|
||||||
|
if self.auth_data != None:
|
||||||
|
topdevicedata = requests.get(
|
||||||
|
"http://"
|
||||||
|
+ self.ip_address
|
||||||
|
+ "/admin/api.php?getQuerySources=25&auth="
|
||||||
|
+ self.auth_data.token
|
||||||
|
).json()
|
||||||
|
|
||||||
|
self.top_devices = topdevicedata["top_sources"]
|
||||||
|
|
||||||
|
self.forward_destinations = requests.get(
|
||||||
|
"http://"
|
||||||
|
+ self.ip_address
|
||||||
|
+ "/admin/api.php?getForwardDestinations&auth="
|
||||||
|
+ self.auth_data.token
|
||||||
|
).json()
|
||||||
|
|
||||||
|
self.query_types = requests.get(
|
||||||
|
"http://"
|
||||||
|
+ self.ip_address
|
||||||
|
+ "/admin/api.php?getQueryTypes&auth="
|
||||||
|
+ self.auth_data.token
|
||||||
|
).json()["querytypes"]
|
||||||
|
|
||||||
|
# Data that is returned is now parsed into vars
|
||||||
|
self.status = rawdata["status"]
|
||||||
|
self.domain_count = rawdata["domains_being_blocked"]
|
||||||
|
self.queries = rawdata["dns_queries_today"]
|
||||||
|
self.blocked = rawdata["ads_blocked_today"]
|
||||||
|
self.ads_percentage = rawdata["ads_percentage_today"]
|
||||||
|
self.unique_domains = rawdata["unique_domains"]
|
||||||
|
self.forwarded = rawdata["queries_forwarded"]
|
||||||
|
self.cached = rawdata["queries_cached"]
|
||||||
|
self.total_clients = rawdata["clients_ever_seen"]
|
||||||
|
self.unique_clients = rawdata["unique_clients"]
|
||||||
|
self.total_queries = rawdata["dns_queries_all_types"]
|
||||||
|
self.gravity_last_updated = rawdata["gravity_last_updated"]
|
||||||
|
|
||||||
|
def refreshTop(self, count):
|
||||||
|
if self.auth_data == None:
|
||||||
|
print("Unable to fetch top items. Please authenticate.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
rawdata = requests.get(
|
||||||
|
"http://"
|
||||||
|
+ self.ip_address
|
||||||
|
+ "/admin/api.php?topItems="
|
||||||
|
+ str(count)
|
||||||
|
+ "&auth="
|
||||||
|
+ self.auth_data.token
|
||||||
|
).json()
|
||||||
|
self.top_queries = rawdata["top_queries"]
|
||||||
|
self.top_ads = rawdata["top_ads"]
|
||||||
|
|
||||||
|
def getGraphData(self):
|
||||||
|
rawdata = requests.get(
|
||||||
|
"http://" + self.ip_address + "/admin/api.php?overTimeData10mins"
|
||||||
|
).json()
|
||||||
|
return {
|
||||||
|
"domains": rawdata["domains_over_time"],
|
||||||
|
"ads": rawdata["ads_over_time"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def authenticate(self, password):
|
||||||
|
self.auth_data = Auth(password)
|
||||||
|
self.pw = password
|
||||||
|
|
||||||
|
# print(self.auth_data.token)
|
||||||
|
|
||||||
|
def getAllQueries(self):
|
||||||
|
if self.auth_data == None:
|
||||||
|
print("Unable to get queries. Please authenticate")
|
||||||
|
exit(1)
|
||||||
|
return requests.get(
|
||||||
|
"http://"
|
||||||
|
+ self.ip_address
|
||||||
|
+ "/admin/api.php?getAllQueries&auth="
|
||||||
|
+ self.auth_data.token
|
||||||
|
).json()["data"]
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
if self.auth_data == None:
|
||||||
|
print("Unable to enable pihole. Please authenticate")
|
||||||
|
exit(1)
|
||||||
|
requests.get(
|
||||||
|
"http://"
|
||||||
|
+ self.ip_address
|
||||||
|
+ "/admin/api.php?enable&auth="
|
||||||
|
+ self.auth_data.token
|
||||||
|
)
|
||||||
|
|
||||||
|
def disable(self, seconds):
|
||||||
|
if self.auth_data == None:
|
||||||
|
print("Unable to disable pihole. Please authenticate")
|
||||||
|
exit(1)
|
||||||
|
requests.get(
|
||||||
|
"http://"
|
||||||
|
+ self.ip_address
|
||||||
|
+ "/admin/api.php?disable="
|
||||||
|
+ str(seconds)
|
||||||
|
+ "&auth="
|
||||||
|
+ self.auth_data.token
|
||||||
|
)
|
||||||
|
|
||||||
|
def getVersion(self):
|
||||||
|
return requests.get(
|
||||||
|
"http://" + self.ip_address + "/admin/api.php?versions"
|
||||||
|
).json()
|
||||||
|
|
||||||
|
def getDBfilesize(self):
|
||||||
|
if self.auth_data == None:
|
||||||
|
print("Please authenticate")
|
||||||
|
exit(1)
|
||||||
|
return float(
|
||||||
|
requests.get(
|
||||||
|
"http://"
|
||||||
|
+ self.ip_address
|
||||||
|
+ "/admin/api_db.php?getDBfilesize&auth="
|
||||||
|
+ self.auth_data.token
|
||||||
|
).json()["filesize"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def getList(self, list):
|
||||||
|
return requests.get(
|
||||||
|
inApiLink(self.ip_address, "get") + "?list=" + str(list)
|
||||||
|
).json()
|
||||||
|
|
||||||
|
def add(self, list, domain):
|
||||||
|
if self.auth_data == None:
|
||||||
|
print("Please authenticate")
|
||||||
|
exit(1)
|
||||||
|
with requests.session() as s:
|
||||||
|
s.get(
|
||||||
|
"http://" + str(self.ip_address) + "/admin/scripts/pi-hole/php/add.php"
|
||||||
|
)
|
||||||
|
requests.post(
|
||||||
|
"http://" + str(self.ip_address) + "/admin/scripts/pi-hole/php/add.php",
|
||||||
|
data={"list": list, "domain": domain, "pw": self.pw},
|
||||||
|
).text
|
||||||
|
|
||||||
|
def sub(self, list, domain):
|
||||||
|
if self.auth_data == None:
|
||||||
|
print("Please authenticate")
|
||||||
|
exit(1)
|
||||||
|
with requests.session() as s:
|
||||||
|
s.get(
|
||||||
|
"http://" + str(self.ip_address) + "/admin/scripts/pi-hole/php/sub.php"
|
||||||
|
)
|
||||||
|
requests.post(
|
||||||
|
"http://" + str(self.ip_address) + "/admin/scripts/pi-hole/php/sub.php",
|
||||||
|
data={"list": list, "domain": domain, "pw": self.pw},
|
||||||
|
).text
|
||||||
|
|
||||||
|
|
||||||
|
class Platform:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# parse the user's options from the config entries
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
self.__dict__[key] = value
|
||||||
|
|
||||||
|
self.pihole = PiHole(self.host)
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
self.pihole.refresh()
|
||||||
|
value_template = render_template_string(
|
||||||
|
self.value_template, **self.pihole.__dict__
|
||||||
|
)
|
||||||
|
return value_template
|
41
dashmachine/platform/ping.py
Normal file
41
dashmachine/platform/ping.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
##### ping
|
||||||
|
Check if a service is online.
|
||||||
|
```ini
|
||||||
|
[variable_name]
|
||||||
|
platform = ping
|
||||||
|
resource = 192.168.1.1
|
||||||
|
```
|
||||||
|
> **Returns:** a right-aligned colored bullet point on the app card.
|
||||||
|
|
||||||
|
| Variable | Required | Description | Options |
|
||||||
|
|-----------------|----------|-----------------------------------------------------------------|-------------------|
|
||||||
|
| [variable_name] | Yes | Name for the data source. | [variable_name] |
|
||||||
|
| plaform | Yes | Name of the platform. | rest |
|
||||||
|
| resource | Yes | Url of whatever you want to ping | url |
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class Platform:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# parse the user's options from the config entries
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
self.__dict__[key] = value
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
param = "-n" if platform.system().lower() == "windows" else "-c"
|
||||||
|
command = ["ping", param, "1", self.resource]
|
||||||
|
up = subprocess.call(command) == 0
|
||||||
|
|
||||||
|
if up is True:
|
||||||
|
icon_class = "theme-success-text"
|
||||||
|
else:
|
||||||
|
icon_class = "theme-failure-text"
|
||||||
|
|
||||||
|
return f"<i class='material-icons right {icon_class}'>fiber_manual_record </i>"
|
85
dashmachine/platform/rest.py
Normal file
85
dashmachine/platform/rest.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
##### rest
|
||||||
|
Make a call on a REST API and display the results as a jinja formatted string.
|
||||||
|
```ini
|
||||||
|
[variable_name]
|
||||||
|
platform = rest
|
||||||
|
resource = https://your-website.com/api
|
||||||
|
value_template = {{value}}
|
||||||
|
method = post
|
||||||
|
authentication = basic
|
||||||
|
username = my_username
|
||||||
|
password = my_password
|
||||||
|
payload = {"var1": "hi", "var2": 1}
|
||||||
|
```
|
||||||
|
> **Returns:** `value_template` as rendered string
|
||||||
|
|
||||||
|
| Variable | Required | Description | Options |
|
||||||
|
|-----------------|----------|-----------------------------------------------------------------|-------------------|
|
||||||
|
| [variable_name] | Yes | Name for the data source. | [variable_name] |
|
||||||
|
| plaform | Yes | Name of the platform. | rest |
|
||||||
|
| resource | Yes | Url of rest api resource. | url |
|
||||||
|
| value_template | Yes | Jinja template for how the returned data from api is displayed. | jinja template |
|
||||||
|
| method | No | Method for the api call, default is GET | GET,POST |
|
||||||
|
| authentication | No | Authentication for the api call, default is None | None,basic,digest |
|
||||||
|
| username | No | Username to use for auth. | string |
|
||||||
|
| password | No | Password to use for auth. | string |
|
||||||
|
| payload | No | Payload for post request. | json |
|
||||||
|
|
||||||
|
> **Working example:**
|
||||||
|
>```ini
|
||||||
|
>[test]
|
||||||
|
>platform = rest
|
||||||
|
>resource = https://pokeapi.co/api/v2/pokemon
|
||||||
|
>value_template = Pokemon: {{value['count']}}
|
||||||
|
>
|
||||||
|
>[Pokemon]
|
||||||
|
>prefix = https://
|
||||||
|
>url = pokemon.com
|
||||||
|
>icon = static/images/apps/default.png
|
||||||
|
>description = Data sources example
|
||||||
|
>open_in = this_tab
|
||||||
|
>data_sources = test
|
||||||
|
>```
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from requests import get, post
|
||||||
|
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
|
||||||
|
from flask import render_template_string
|
||||||
|
|
||||||
|
|
||||||
|
class Platform:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# parse the user's options from the config entries
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
self.__dict__[key] = value
|
||||||
|
|
||||||
|
# set defaults for omitted options
|
||||||
|
if not hasattr(self, "method"):
|
||||||
|
self.method = "GET"
|
||||||
|
if not hasattr(self, "authentication"):
|
||||||
|
self.authentication = None
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
if self.method.upper() == "GET":
|
||||||
|
try:
|
||||||
|
value = get(self.resource).json()
|
||||||
|
except Exception as e:
|
||||||
|
value = f"{e}"
|
||||||
|
|
||||||
|
elif self.method.upper() == "POST":
|
||||||
|
if self.authentication:
|
||||||
|
if self.authentication.lower() == "digest":
|
||||||
|
auth = HTTPDigestAuth(self.username, self.password)
|
||||||
|
else:
|
||||||
|
auth = HTTPBasicAuth(self.username, self.password)
|
||||||
|
else:
|
||||||
|
auth = None
|
||||||
|
|
||||||
|
payload = json.loads(self.payload.replace("'", '"'))
|
||||||
|
value = post(self.resource, data=payload, auth=auth)
|
||||||
|
value_template = render_template_string(self.value_template, value=value)
|
||||||
|
return value_template
|
38
dashmachine/platform/transmission.py
Normal file
38
dashmachine/platform/transmission.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import json
|
||||||
|
from flask import render_template_string
|
||||||
|
import transmissionrpc
|
||||||
|
|
||||||
|
|
||||||
|
# from pprint import PrettyPrinter
|
||||||
|
# pp = PrettyPrinter()
|
||||||
|
|
||||||
|
|
||||||
|
class Platform:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# parse the user's options from the config entries
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
self.__dict__[key] = value
|
||||||
|
|
||||||
|
if not hasattr(self, "port"):
|
||||||
|
self.port = 9091
|
||||||
|
if not hasattr(self, "host"):
|
||||||
|
self.host = "localhost"
|
||||||
|
|
||||||
|
self.tc = transmissionrpc.Client(
|
||||||
|
self.host, port=self.port, user=self.user, password=self.password
|
||||||
|
)
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
|
||||||
|
torrents = len(self.tc.get_torrents())
|
||||||
|
data = {}
|
||||||
|
for key, field in self.tc.session_stats().__dict__["_fields"].items():
|
||||||
|
data[key] = field.value
|
||||||
|
# pp.pprint (data)
|
||||||
|
|
||||||
|
value_template = render_template_string(self.value_template, **data)
|
||||||
|
return value_template
|
||||||
|
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
# test = Platform(host='192.168.1.19', user='', password='').process()
|
@ -1,5 +1,3 @@
|
|||||||
import os
|
|
||||||
from flask import request
|
|
||||||
from flask_restful import Resource
|
from flask_restful import Resource
|
||||||
from dashmachine.version import version
|
from dashmachine.version import version
|
||||||
|
|
||||||
@ -7,15 +5,3 @@ from dashmachine.version import version
|
|||||||
class GetVersion(Resource):
|
class GetVersion(Resource):
|
||||||
def get(self):
|
def get(self):
|
||||||
return {"Version": version}
|
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"}
|
|
||||||
|
@ -6,7 +6,6 @@ class Settings(db.Model):
|
|||||||
theme = db.Column(db.String())
|
theme = db.Column(db.String())
|
||||||
accent = db.Column(db.String())
|
accent = db.Column(db.String())
|
||||||
background = db.Column(db.String())
|
background = db.Column(db.String())
|
||||||
|
roles = db.Column(db.String())
|
||||||
|
home_access_groups = db.Column(db.String())
|
||||||
db.create_all()
|
settings_access_groups = db.Column(db.String())
|
||||||
db.session.commit()
|
|
||||||
|
@ -1,29 +1,46 @@
|
|||||||
import os
|
import os
|
||||||
from shutil import move
|
from shutil import move
|
||||||
from flask import render_template, request, Blueprint, jsonify
|
from flask_login import current_user
|
||||||
from dashmachine.settings_system.forms import ConfigForm
|
from flask import render_template, request, Blueprint, jsonify, redirect, url_for
|
||||||
from dashmachine.user_system.forms import UserForm
|
from dashmachine.user_system.forms import UserForm
|
||||||
from dashmachine.user_system.utils import add_edit_user
|
from dashmachine.user_system.utils import add_edit_user
|
||||||
from dashmachine.main.utils import read_config, row2dict
|
from dashmachine.user_system.models import User
|
||||||
|
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.main.models import Files, TemplateApps
|
||||||
from dashmachine.paths import backgrounds_images_folder, icons_images_folder
|
from dashmachine.settings_system.forms import ConfigForm
|
||||||
|
from dashmachine.settings_system.utils import load_files_html, get_config_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
|
from dashmachine.version import version
|
||||||
from dashmachine.settings_system.utils import load_files_html
|
from dashmachine import db
|
||||||
|
|
||||||
settings_system = Blueprint("settings_system", __name__)
|
settings_system = Blueprint("settings_system", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@public_route
|
||||||
@settings_system.route("/settings", methods=["GET"])
|
@settings_system.route("/settings", methods=["GET"])
|
||||||
def settings():
|
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()
|
config_form = ConfigForm()
|
||||||
user_form = UserForm()
|
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()
|
config_form.config.data = config_file.read()
|
||||||
files_html = load_files_html()
|
files_html = load_files_html()
|
||||||
template_apps = []
|
template_apps = []
|
||||||
t_apps = TemplateApps.query.all()
|
t_apps = TemplateApps.query.all()
|
||||||
for t_app in t_apps:
|
for t_app in t_apps:
|
||||||
template_apps.append(f"{t_app.name}&&{t_app.icon}")
|
template_apps.append(f"{t_app.name}&&{t_app.icon}")
|
||||||
|
|
||||||
|
users = User.query.all()
|
||||||
|
config_readme = get_config_html()
|
||||||
return render_template(
|
return render_template(
|
||||||
"settings_system/settings.html",
|
"settings_system/settings.html",
|
||||||
config_form=config_form,
|
config_form=config_form,
|
||||||
@ -31,12 +48,14 @@ def settings():
|
|||||||
user_form=user_form,
|
user_form=user_form,
|
||||||
template_apps=",".join(template_apps),
|
template_apps=",".join(template_apps),
|
||||||
version=version,
|
version=version,
|
||||||
|
users=users,
|
||||||
|
config_readme=config_readme,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@settings_system.route("/settings/save_config", methods=["POST"])
|
@settings_system.route("/settings/save_config", methods=["POST"])
|
||||||
def save_config():
|
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"))
|
config_file.write(request.form.get("config"))
|
||||||
msg = read_config()
|
msg = read_config()
|
||||||
return jsonify(data=msg)
|
return jsonify(data=msg)
|
||||||
@ -81,7 +100,14 @@ def edit_user():
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if form.password.data != form.confirm_password.data:
|
if form.password.data != form.confirm_password.data:
|
||||||
return jsonify(data={"err": "Passwords don't match"})
|
return jsonify(data={"err": "Passwords don't match"})
|
||||||
add_edit_user(form.username.data, form.password.data)
|
err = add_edit_user(
|
||||||
|
form.username.data,
|
||||||
|
form.password.data,
|
||||||
|
user_id=form.id.data,
|
||||||
|
role=form.role.data,
|
||||||
|
)
|
||||||
|
if err:
|
||||||
|
return jsonify(data={"err": err})
|
||||||
else:
|
else:
|
||||||
err_str = ""
|
err_str = ""
|
||||||
for fieldName, errorMessages in form.errors.items():
|
for fieldName, errorMessages in form.errors.items():
|
||||||
@ -89,4 +115,20 @@ def edit_user():
|
|||||||
for err in errorMessages:
|
for err in errorMessages:
|
||||||
err_str += f"{err} "
|
err_str += f"{err} "
|
||||||
return jsonify(data={"err": err_str})
|
return jsonify(data={"err": err_str})
|
||||||
return jsonify(data={"err": "success"})
|
users = User.query.all()
|
||||||
|
html = render_template("settings_system/user.html", users=users)
|
||||||
|
return jsonify(data={"err": "success", "html": html})
|
||||||
|
|
||||||
|
|
||||||
|
@settings_system.route("/settings/delete_user", methods=["GET"])
|
||||||
|
def delete_user():
|
||||||
|
admin_users = User.query.filter_by(role="admin").all()
|
||||||
|
user = User.query.filter_by(id=request.args.get("id")).first()
|
||||||
|
if len(admin_users) < 2 and user.role == "admin":
|
||||||
|
return jsonify(data={"err": "You must have at least one admin user"})
|
||||||
|
else:
|
||||||
|
User.query.filter_by(id=request.args.get("id")).delete()
|
||||||
|
db.session.commit()
|
||||||
|
users = User.query.all()
|
||||||
|
html = render_template("settings_system/user.html", users=users)
|
||||||
|
return jsonify(data={"err": "success", "html": html})
|
||||||
|
@ -1,11 +1,43 @@
|
|||||||
from dashmachine.paths import backgrounds_images_folder, icons_images_folder
|
import os
|
||||||
|
import importlib
|
||||||
|
from markdown2 import markdown
|
||||||
|
from dashmachine.paths import (
|
||||||
|
backgrounds_images_folder,
|
||||||
|
icons_images_folder,
|
||||||
|
root_folder,
|
||||||
|
platform_folder,
|
||||||
|
)
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
from os import listdir
|
|
||||||
|
|
||||||
|
|
||||||
def load_files_html():
|
def load_files_html():
|
||||||
backgrounds = listdir(backgrounds_images_folder)
|
backgrounds = os.listdir(backgrounds_images_folder)
|
||||||
icons = listdir(icons_images_folder)
|
icons = os.listdir(icons_images_folder)
|
||||||
return render_template(
|
return render_template(
|
||||||
"settings_system/files.html", backgrounds=backgrounds, icons=icons,
|
"settings_system/files.html", backgrounds=backgrounds, icons=icons,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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(
|
||||||
|
md,
|
||||||
|
extras=[
|
||||||
|
"tables",
|
||||||
|
"fenced-code-blocks",
|
||||||
|
"break-on-newline",
|
||||||
|
"header-ids",
|
||||||
|
"code-friendly",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return config_html
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
from jsmin import jsmin
|
from jsmin import jsmin
|
||||||
|
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
|
||||||
|
from dashmachine.main.utils import check_groups
|
||||||
from dashmachine.settings_system.models import Settings
|
from dashmachine.settings_system.models import Settings
|
||||||
from dashmachine.paths import static_folder, backgrounds_images_folder
|
from dashmachine.paths import static_folder, backgrounds_images_folder
|
||||||
from dashmachine.cssmin import cssmin
|
from dashmachine.cssmin import cssmin
|
||||||
@ -72,9 +74,19 @@ def process_css_sources(process_bundle=None, src=None, app_global=False):
|
|||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def 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()
|
settings = Settings.query.first()
|
||||||
if settings.background == "random":
|
if settings.background == "random":
|
||||||
|
if len(os.listdir(backgrounds_images_folder)) < 1:
|
||||||
|
settings.background = None
|
||||||
|
else:
|
||||||
settings.background = (
|
settings.background = (
|
||||||
f"static/images/backgrounds/"
|
f"static/images/backgrounds/"
|
||||||
f"{random.choice(os.listdir(backgrounds_images_folder))}"
|
f"{random.choice(os.listdir(backgrounds_images_folder))}"
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
--theme-color-font: #2c2f3a;
|
--theme-color-font: #2c2f3a;
|
||||||
--theme-color-font-muted: rgba(44, 47, 58, 0.85);
|
--theme-color-font-muted: rgba(44, 47, 58, 0.85);
|
||||||
--theme-color-font-muted2: rgba(44, 47, 58, 0.65);
|
--theme-color-font-muted2: rgba(44, 47, 58, 0.65);
|
||||||
--theme-warning: #f44336;
|
--theme-failure: #f44336;
|
||||||
|
--theme-warning: #ffae42;
|
||||||
|
--theme-success: #4BB543;
|
||||||
--theme-on-primary: #fff;
|
--theme-on-primary: #fff;
|
||||||
}
|
}
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
@ -117,12 +119,24 @@
|
|||||||
.theme-text {
|
.theme-text {
|
||||||
color: var(--theme-color-font) !important;
|
color: var(--theme-color-font) !important;
|
||||||
}
|
}
|
||||||
|
.theme-failure {
|
||||||
|
background-color: var(--theme-failure) !important;
|
||||||
|
}
|
||||||
|
.theme-failure-text {
|
||||||
|
color: var(--theme-failure) !important;
|
||||||
|
}
|
||||||
.theme-warning {
|
.theme-warning {
|
||||||
background-color: var(--theme-warning) !important;
|
background-color: var(--theme-warning) !important;
|
||||||
}
|
}
|
||||||
.theme-warning-text {
|
.theme-warning-text {
|
||||||
color: var(--theme-warning) !important;
|
color: var(--theme-warning) !important;
|
||||||
}
|
}
|
||||||
|
.theme-success {
|
||||||
|
background-color: var(--theme-success) !important;
|
||||||
|
}
|
||||||
|
.theme-success-text {
|
||||||
|
color: var(--theme-success) !important;
|
||||||
|
}
|
||||||
.theme-muted-text {
|
.theme-muted-text {
|
||||||
color: var(--theme-color-font-muted) !important;
|
color: var(--theme-color-font-muted) !important;
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,9 @@
|
|||||||
.no-vis {
|
.no-vis {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
.filtered {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.scrollbar {
|
.scrollbar {
|
||||||
overflow-y: scroll !important;
|
overflow-y: scroll !important;
|
||||||
|
23
dashmachine/static/css/main/home.css
Normal file
23
dashmachine/static/css/main/home.css
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
.tags-select-col {
|
||||||
|
position: relative;
|
||||||
|
top: 15px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: .4rem;
|
||||||
|
height: 45px;
|
||||||
|
-webkit-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.tags-select-col .select-wrapper {
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
.tags-select-col .select-wrapper input {
|
||||||
|
color: var(--theme-secondary);
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 992px) {
|
||||||
|
.tags-select-col {
|
||||||
|
top: 0;
|
||||||
|
width: calc(100vw - 45px) !important;
|
||||||
|
margin-left: 15px !important;
|
||||||
|
}
|
||||||
|
}
|
@ -16,3 +16,39 @@
|
|||||||
border-top-right-radius: 0px;
|
border-top-right-radius: 0px;
|
||||||
background: var(--theme-surface-1);
|
background: var(--theme-surface-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#config-readme h5 {
|
||||||
|
color: var(--theme-primary);
|
||||||
|
margin-top: 5%;
|
||||||
|
}
|
||||||
|
#config-readme h4 {
|
||||||
|
color: var(--theme-color-font-muted);
|
||||||
|
margin-top: 5%;
|
||||||
|
}
|
||||||
|
#configini-readme {
|
||||||
|
margin-top: 2% !important;
|
||||||
|
}
|
||||||
|
#config-readme code {
|
||||||
|
-webkit-touch-callout: all;
|
||||||
|
-webkit-user-select: all;
|
||||||
|
-khtml-user-select: all;
|
||||||
|
-moz-user-select: all;
|
||||||
|
-ms-user-select: all;
|
||||||
|
user-select: all;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
#config-readme th {
|
||||||
|
color: var(--theme-primary);
|
||||||
|
}
|
||||||
|
#config-readme td {
|
||||||
|
-webkit-touch-callout: text !important;
|
||||||
|
-webkit-user-select: text !important;
|
||||||
|
-khtml-user-select: text !important;
|
||||||
|
-moz-user-select: text !important;
|
||||||
|
-ms-user-select: text !important;
|
||||||
|
user-select: text !important;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
#config-readme strong {
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
@ -15,15 +15,26 @@ $( document ).ready(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".data-template").each(function(e) {
|
$(".data-source-container").each(function(e) {
|
||||||
var el = $(this);
|
var el = $(this);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: el.attr('data-url'),
|
url: el.attr('data-url'),
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
data: {template: el.text()},
|
data: {id: el.attr('data-id')},
|
||||||
success: function(data){
|
success: function(data){
|
||||||
el.text(data);
|
el.closest('.col').find('.data-source-loading').addClass('hide');
|
||||||
el.removeClass('hide');
|
el.html(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#tags-select").on('change', function(e) {
|
||||||
|
var value = $(this).val();
|
||||||
|
$(".app-a").each(function(i, e) {
|
||||||
|
if ($(this).attr("data-tags").indexOf(value) > -1 || value === "All tags") {
|
||||||
|
$(this).removeClass('filtered');
|
||||||
|
} else {
|
||||||
|
$(this).addClass('filtered');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,11 @@ d.className += " active theme-primary";
|
|||||||
|
|
||||||
$( document ).ready(function() {
|
$( document ).ready(function() {
|
||||||
initTCdrop('#images-tcdrop');
|
initTCdrop('#images-tcdrop');
|
||||||
$("#config-wiki-modal").modal();
|
$("#user-modal").modal({
|
||||||
|
onCloseEnd: function () {
|
||||||
|
$("#edit-user-form").trigger('reset');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$("#save-config-btn").on('click', function(e) {
|
$("#save-config-btn").on('click', function(e) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -15,7 +19,7 @@ $( document ).ready(function() {
|
|||||||
M.toast({html: 'Config applied successfully'});
|
M.toast({html: 'Config applied successfully'});
|
||||||
location.reload(true);
|
location.reload(true);
|
||||||
} else {
|
} else {
|
||||||
M.toast({html: data.data.msg, classes: "theme-warning"});
|
M.toast({html: data.data.msg, classes: "theme-failure"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -58,18 +62,19 @@ $( document ).ready(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#edit-user-btn").on('click', function(e) {
|
$("#save-user-btn").on('click', function(e) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: $(this).attr('data-url'),
|
url: $(this).attr('data-url'),
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: $("#edit-user-form").serialize(),
|
data: $("#edit-user-form").serialize(),
|
||||||
success: function(data){
|
success: function(data){
|
||||||
if (data.data.err !== 'success'){
|
if (data.data.err !== 'success'){
|
||||||
M.toast({html: data.data.err, classes: 'theme-warning'});
|
M.toast({html: data.data.err, classes: 'theme-failure'});
|
||||||
} else {
|
} else {
|
||||||
$("#user-form-password").val('');
|
$("#users-div").empty();
|
||||||
$("#user-form-confirm_password").val('');
|
$("#users-div").append(data.data.html);
|
||||||
M.toast({html: 'User updated'});
|
$("#user-modal").modal('close');
|
||||||
|
M.toast({html: 'User saved'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
11
dashmachine/templates/error_pages/unauthorized.html
Normal file
11
dashmachine/templates/error_pages/unauthorized.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends "main/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row center-align mt-10">
|
||||||
|
<div class="col s12">
|
||||||
|
<i class="material-icons-outlined theme-failure-text" style="font-size: 8rem">warning</i>
|
||||||
|
<h4>Unauthorized</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -169,10 +169,13 @@ col_style=None
|
|||||||
id='',
|
id='',
|
||||||
form_obj=None,
|
form_obj=None,
|
||||||
size="s12",
|
size="s12",
|
||||||
label=''
|
label=None,
|
||||||
|
class=''
|
||||||
) %}
|
) %}
|
||||||
<div class="input-field col {{size}}">
|
<div class="input-field col {{size}}">
|
||||||
{{ form_obj(id=id) }}
|
{{ form_obj(id=id, class=class, placeholder="Tags") }}
|
||||||
|
{% if label %}
|
||||||
<label>{{ label }}</label>
|
<label>{{ label }}</label>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
@ -1,10 +1,11 @@
|
|||||||
{% extends "main/layout.html" %}
|
{% extends "main/layout.html" %}
|
||||||
{% from 'global_macros.html' import data %}
|
{% from 'global_macros.html' import data, preload_circle, select %}
|
||||||
|
|
||||||
{% block page_vendor_css %}
|
{% block page_vendor_css %}
|
||||||
{% endblock page_vendor_css %}
|
{% endblock page_vendor_css %}
|
||||||
|
|
||||||
{% block page_lvl_css %}
|
{% block page_lvl_css %}
|
||||||
|
{{ process_css_sources(src="main/home.css")|safe }}
|
||||||
{% if settings.background and settings.background != 'None' %}
|
{% if settings.background and settings.background != 'None' %}
|
||||||
<style>
|
<style>
|
||||||
#main {
|
#main {
|
||||||
@ -27,31 +28,40 @@
|
|||||||
<input type="text" id="apps-filter" class="card-filter theme-surface-transparent" placeholder="Search apps">
|
<input type="text" id="apps-filter" class="card-filter theme-surface-transparent" placeholder="Search apps">
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{% if tags_form.tags.choices|count > 1 %}
|
||||||
|
<div class="input-field col s12 l2 tags-select-col theme-surface-transparent">
|
||||||
|
{{ tags_form.tags(id='tags-select') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if apps %}
|
{% if apps %}
|
||||||
|
|
||||||
{% for app in apps %}
|
{% for app in apps %}
|
||||||
{% if app.open_in == 'iframe' %}
|
{% if app.open_in == 'iframe' %}
|
||||||
<a href="{{ url_for('main.app_view', url=app.url) }}" class="app-a" data-name="{{ app.name }}" data-description="{{ app.description }}">
|
<a href="{{ url_for('main.app_view', app_id=app.id) }}" class="app-a" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}">
|
||||||
{% elif app.open_in == 'this_tab' %}
|
{% elif app.open_in == 'this_tab' %}
|
||||||
<a href="{{ app.prefix }}{{ app.url }}" class="app-a" data-name="{{ app.name }}" data-description="{{ app.description }}">
|
<a href="{{ app.prefix }}{{ app.url }}" class="app-a" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}">
|
||||||
{% elif app.open_in == "new_tab" %}
|
{% elif app.open_in == "new_tab" %}
|
||||||
<a href="{{ app.prefix }}{{ app.url }}" target="_blank" class="app-a" data-name="{{ app.name }}" data-description="{{ app.description }}">
|
<a href="{{ app.prefix }}{{ app.url }}" target="_blank" class="app-a" data-name="{{ app.name }}" data-description="{{ app.description }}" data-tags="{{ app.tags }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="col s12 m6 l3">
|
<div class="col s12 m6 l3">
|
||||||
<div class="card theme-surface-transparent app-card">
|
<div class="card theme-surface-transparent app-card">
|
||||||
<div class="card-content center-align scrollbar" style="max-height: 118px; min-height: 118px; scrollbar-width: none;">
|
<div class="card-content center-align scrollbar" style="max-height: 118px; min-height: 118px; scrollbar-width: none;">
|
||||||
{% if app.data_template %}
|
{% 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">
|
||||||
<p class="data-template hide theme-text" data-url="{{ url_for('main.load_rest_data') }}" style="white-space: pre-line">
|
<span class="data-source-loading">{{ preload_circle() }}</span>
|
||||||
{{ app.data_template|safe }}
|
{% 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>
|
</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -59,7 +69,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-action center-align scrollbar" style="max-height: 127px; min-height: 127px; scrollbar-width: none;">
|
<div class="card-action center-align scrollbar" style="max-height: 127px; min-height: 127px; scrollbar-width: none;">
|
||||||
<h5>{{ app.name }}</h5>
|
<h5>{{ app.name }}
|
||||||
|
</h5>
|
||||||
<span class="theme-secondary-text">{{ app.description }}</span>
|
<span class="theme-secondary-text">{{ app.description }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,10 +24,17 @@
|
|||||||
<span class="menu-title" data-i18n="">Settings</span>
|
<span class="menu-title" data-i18n="">Settings</span>
|
||||||
</a></li>
|
</a></li>
|
||||||
|
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
<li class="bold"><a id="logout-sidenav" class="waves-effect waves-cyan" href="{{ url_for('user_system.logout') }}">
|
<li class="bold"><a id="logout-sidenav" class="waves-effect waves-cyan" href="{{ url_for('user_system.logout') }}">
|
||||||
<i class="material-icons-outlined">exit_to_app</i>
|
<i class="material-icons-outlined">exit_to_app</i>
|
||||||
<span class="menu-title" data-i18n="">Logout</span>
|
<span class="menu-title" data-i18n="">Logout</span>
|
||||||
</a></li>
|
</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="bold"><a id="logout-sidenav" class="waves-effect waves-cyan" href="{{ url_for('user_system.login') }}">
|
||||||
|
<i class="material-icons-outlined">account_circle</i>
|
||||||
|
<span class="menu-title" data-i18n="">Login</span>
|
||||||
|
</a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<li class="bold"><a id="hide-sidenav" class="waves-effect waves-cyan" href="#">
|
<li class="bold"><a id="hide-sidenav" class="waves-effect waves-cyan" href="#">
|
||||||
<i class="material-icons-outlined">menu_open</i>
|
<i class="material-icons-outlined">menu_open</i>
|
||||||
@ -37,7 +44,7 @@
|
|||||||
{% for app in apps %}
|
{% for app in apps %}
|
||||||
<li class="bold">
|
<li class="bold">
|
||||||
{% if app.open_in == 'iframe' %}
|
{% if app.open_in == 'iframe' %}
|
||||||
<a id="dashboard-sidenav" class="waves-effect waves-cyan" href="{{ url_for('main.app_view', url=app.url) }}">
|
<a id="dashboard-sidenav" class="waves-effect waves-cyan" href="{{ url_for('main.app_view', app_id=app.id) }}">
|
||||||
{% elif app.open_in == "this_tab" %}
|
{% elif app.open_in == "this_tab" %}
|
||||||
<a id="dashboard-sidenav" class="waves-effect waves-cyan" href="{{ app.prefix }}{{ app.url }}">
|
<a id="dashboard-sidenav" class="waves-effect waves-cyan" href="{{ app.prefix }}{{ app.url }}">
|
||||||
{% elif app.open_in == "new_tab" %}
|
{% elif app.open_in == "new_tab" %}
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
{{ preload_circle() }}
|
{{ preload_circle() }}
|
||||||
</li>
|
</li>
|
||||||
<li class="tcdrop-error-msg tcdrop-li hide">
|
<li class="tcdrop-error-msg tcdrop-li hide">
|
||||||
<i class="tcdrop-delete-file material-icons theme-warning">error</i>
|
<i class="tcdrop-delete-file material-icons theme-failure">error</i>
|
||||||
<span class="tcdrop-error-msg-txt file-name theme-warning-text"></span>
|
<span class="tcdrop-error-msg-txt file-name theme-failure-text"></span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,28 @@
|
|||||||
|
{% macro FilesTab() %}
|
||||||
|
<div class="row">
|
||||||
|
<h5>Images</h5>
|
||||||
|
<form id="add-images-form">
|
||||||
|
<div class="input-field col s12 mt-4">
|
||||||
|
<select name="folder">
|
||||||
|
<option value="icons">Icons</option>
|
||||||
|
<option value="backgrounds">Backgrounds</option>
|
||||||
|
</select>
|
||||||
|
<label>Folder</label>
|
||||||
|
</div>
|
||||||
|
<input name="files" id="add-images-input" class="hide">
|
||||||
|
</form>
|
||||||
|
<div class="col s12">
|
||||||
|
{{ tcdrop(allowed_types='jpg,jpeg,png,gif', id="images-tcdrop", max_files="30") }}
|
||||||
|
{{ button(text="save", icon="save", id="save-images-btn", float="left", data={"url": url_for('settings_system.add_images')}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div id="files-div">{{ files_html|safe }}</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro Files(icons, backgrounds) %}
|
||||||
<style>
|
<style>
|
||||||
.file-title {
|
.file-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -76,3 +101,6 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{{ Files(icons, backgrounds) }}
|
@ -1,6 +1,8 @@
|
|||||||
{% extends "main/layout.html" %}
|
{% extends "main/layout.html" %}
|
||||||
{% from 'global_macros.html' import input, button %}
|
{% from 'global_macros.html' import input, button, select %}
|
||||||
{% from 'main/tcdrop.html' import tcdrop %}
|
{% from 'main/tcdrop.html' import tcdrop %}
|
||||||
|
{% from 'settings_system/user.html' import UserTab with context %}
|
||||||
|
{% from 'settings_system/files.html' import FilesTab with context %}
|
||||||
{% block page_vendor_css %}
|
{% block page_vendor_css %}
|
||||||
{% endblock page_vendor_css %}
|
{% endblock page_vendor_css %}
|
||||||
|
|
||||||
@ -19,234 +21,6 @@
|
|||||||
{% endblock page_lvl_css %}
|
{% endblock page_lvl_css %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="config-wiki-modal" class="modal full-height-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col s12">
|
|
||||||
<h4>Config.ini Readme
|
|
||||||
<span><i class="material-icons-outlined modal-close right icon-btn">close</i></span>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="config-help-col" class="col s12 theme-surface-1 padding-2 border-radius-10">
|
|
||||||
<h5>Settings Reference</h5>
|
|
||||||
<code class="selectable-all">
|
|
||||||
[Settings]<br>
|
|
||||||
theme = dark<br>
|
|
||||||
accent = orange<br>
|
|
||||||
background = static/images/backgrounds/background.png<br>
|
|
||||||
</code>
|
|
||||||
|
|
||||||
<table class="mt-4 responsive-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Variable</th>
|
|
||||||
<th>Required</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Options</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="selectable">
|
|
||||||
<tr>
|
|
||||||
<td>[Settings]</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>Config section name.</td>
|
|
||||||
<td>string</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>theme</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>UI theme</td>
|
|
||||||
<td>light, dark</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>accent</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>UI accent color</td>
|
|
||||||
<td>
|
|
||||||
orange, red, pink, purple, deepPurple, indigo, blue, lightBlue,
|
|
||||||
cyan, teal, green, lightGreen, lime, yellow, amber, deepOrange, brown, grey, blueGrey
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>background</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>Background image for the UI</td>
|
|
||||||
<td>/static/images/backgrounds/yourpicture.png, external link to image, None, random</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h5>App Reference</h5>
|
|
||||||
<code class="selectable-all">
|
|
||||||
[App Name]<br>
|
|
||||||
prefix = https://<br>
|
|
||||||
url = your-website.com<br>
|
|
||||||
icon = static/images/apps/default.png<br>
|
|
||||||
sidebar_icon = static/images/apps/default.png<br>
|
|
||||||
description = Example description<br>
|
|
||||||
open_in = iframe<br>
|
|
||||||
data_template = None
|
|
||||||
</code>
|
|
||||||
|
|
||||||
<table class="mt-4 responsive-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Variable</th>
|
|
||||||
<th>Required</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Options</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="selectable">
|
|
||||||
<tr>
|
|
||||||
<td>[App Name]</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>The name of your app.</td>
|
|
||||||
<td>string</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>prefix</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>The prefix for the app's url.</td>
|
|
||||||
<td>web prefix, e.g. http:// or https://</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>url</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>The url for your app.</td>
|
|
||||||
<td>web url, e.g. myapp.com</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>icon</td>
|
|
||||||
<td>No</td>
|
|
||||||
<td>Icon for the dashboard.</td>
|
|
||||||
<td>/static/images/icons/yourpicture.png, external link to image</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>sidebar_icon</td>
|
|
||||||
<td>No</td>
|
|
||||||
<td>Icon for the sidenav.</td>
|
|
||||||
<td>/static/images/icons/yourpicture.png, external link to image</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>description</td>
|
|
||||||
<td>No</td>
|
|
||||||
<td>A short description for the app.</td>
|
|
||||||
<td>string</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>open_in</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>open the app in the current tab, an iframe or a new tab</td>
|
|
||||||
<td>iframe, new_tab, this_tab</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>data_template</td>
|
|
||||||
<td>No</td>
|
|
||||||
<td>Template for displaying variable(s) from rest data *Note: you must have a rest data variable set up in the config</td>
|
|
||||||
<td>example: Data: {{ '{{ your_variable }}' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h5>Api Data Reference</h5>
|
|
||||||
<code class="selectable-all">
|
|
||||||
[variable_name]<br>
|
|
||||||
platform = rest<br>
|
|
||||||
resource = your-website.com<br>
|
|
||||||
value_template = variable_name<br>
|
|
||||||
</code>
|
|
||||||
|
|
||||||
<table class="mt-4 responsive-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Variable</th>
|
|
||||||
<th>Required</th>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Options</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="selectable">
|
|
||||||
<tr>
|
|
||||||
<td>[variable_name]</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>The variable to be made available to apps.</td>
|
|
||||||
<td>variable (python syntax)</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>platform</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>Platform for data source</td>
|
|
||||||
<td>rest</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>resource</td>
|
|
||||||
<td>Yes</td>
|
|
||||||
<td>The url for the api call.</td>
|
|
||||||
<td>myapp.com/api/hello</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>value_template</td>
|
|
||||||
<td>No</td>
|
|
||||||
<td>Tranform the data returned by the api call (python syntax)</td>
|
|
||||||
<td>variable_name[0]['info']</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>method</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>payload</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>authentication</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>username</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>password</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
<td>NOT IMPLEMENTED</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<h5>Api Data Example</h5>
|
|
||||||
<p>Say we wanted to display how many Pokemon there are using the PokeAPI, we would add the following to the config:</p>
|
|
||||||
<code class="selectable-all">
|
|
||||||
[num_pokemon]<br>
|
|
||||||
platform = rest<br>
|
|
||||||
resource = https://pokeapi.co/api/v2/pokemon<br>
|
|
||||||
value_template = num_pokemon['count']<br>
|
|
||||||
</code>
|
|
||||||
|
|
||||||
<p>Then in the config entry for the app you want to add this to, you would add:</p>
|
|
||||||
|
|
||||||
<code class="selectable-all">
|
|
||||||
data_template = Pokemon: {{ '{{ num_pokemon }}' }}
|
|
||||||
</code>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main" class="main-full">
|
<div id="main" class="main-full">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -256,11 +30,7 @@
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h5>Config
|
<h5>Config.ini</h5>
|
||||||
<a href="#config-wiki-modal" class="modal-trigger">
|
|
||||||
<i class="material-icons-outlined theme-secondary-text icon-btn ml-2 toggle-config-help" style="position: relative; top: 4px;">info</i>
|
|
||||||
</a>
|
|
||||||
</h5>
|
|
||||||
{{ button(
|
{{ button(
|
||||||
icon="save",
|
icon="save",
|
||||||
id="save-config-btn",
|
id="save-config-btn",
|
||||||
@ -292,6 +62,10 @@
|
|||||||
<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">
|
||||||
|
<i class="material-icons-outlined">info</i>
|
||||||
|
</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>
|
||||||
@ -307,28 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="images" class="col s12">
|
<div id="images" class="col s12">
|
||||||
<div class="row">
|
{{ FilesTab() }}
|
||||||
<h5>Images</h5>
|
|
||||||
<form id="add-images-form">
|
|
||||||
<div class="input-field col s12 mt-4">
|
|
||||||
<select name="folder">
|
|
||||||
<option value="icons">Icons</option>
|
|
||||||
<option value="backgrounds">Backgrounds</option>
|
|
||||||
</select>
|
|
||||||
<label>Folder</label>
|
|
||||||
</div>
|
|
||||||
<input name="files" id="add-images-input" class="hide">
|
|
||||||
</form>
|
|
||||||
<div class="col s12">
|
|
||||||
{{ tcdrop(allowed_types='jpg,jpeg,png,gif', id="images-tcdrop", max_files="30") }}
|
|
||||||
{{ button(text="save", icon="save", id="save-images-btn", float="left", data={"url": url_for('settings_system.add_images')}) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div id="files-div">{{ files_html|safe }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="apps" class="col s12">
|
<div id="apps" class="col s12">
|
||||||
@ -350,54 +103,15 @@
|
|||||||
<div id="template-div" class="selectable-all code"></div>
|
<div id="template-div" class="selectable-all code"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="config-readme" class="col s12">
|
||||||
|
{{ config_readme|safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="user" class="col s12">
|
<div id="user" class="col s12">
|
||||||
<div class="row">
|
{{ UserTab() }}
|
||||||
<h5>User</h5>
|
|
||||||
|
|
||||||
<form id="edit-user-form">
|
|
||||||
{{ user_form.hidden_tag() }}
|
|
||||||
|
|
||||||
{{ input(
|
|
||||||
label="Username",
|
|
||||||
id="user-form-username",
|
|
||||||
size="s12",
|
|
||||||
form_obj=user_form.username,
|
|
||||||
val=current_user.username
|
|
||||||
) }}
|
|
||||||
|
|
||||||
{{ input(
|
|
||||||
label="Password",
|
|
||||||
id="user-form-password",
|
|
||||||
form_obj=user_form.password,
|
|
||||||
size="s12"
|
|
||||||
) }}
|
|
||||||
|
|
||||||
{{ input(
|
|
||||||
label="Confirm Password",
|
|
||||||
id="user-form-confirm_password",
|
|
||||||
form_obj=user_form.confirm_password,
|
|
||||||
required='required',
|
|
||||||
size="s12"
|
|
||||||
) }}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{{ button(
|
|
||||||
icon="save",
|
|
||||||
float="left",
|
|
||||||
id="edit-user-btn",
|
|
||||||
data={'url': url_for('settings_system.edit_user')},
|
|
||||||
text="save"
|
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mt-4">
|
|
||||||
<h5>DashMachine</h5>
|
|
||||||
<p class="mb-2">version: {{ version }}</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
130
dashmachine/templates/settings_system/user.html
Normal file
130
dashmachine/templates/settings_system/user.html
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
{% macro UserTab() %}
|
||||||
|
<div id="user-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col s12">
|
||||||
|
<form id="edit-user-form">
|
||||||
|
{{ user_form.hidden_tag() }}
|
||||||
|
|
||||||
|
{{ select(
|
||||||
|
id='user-form-role',
|
||||||
|
form_obj=user_form.role,
|
||||||
|
size="s12",
|
||||||
|
label='Role'
|
||||||
|
) }}
|
||||||
|
|
||||||
|
{{ input(
|
||||||
|
label="Username",
|
||||||
|
id="user-form-username",
|
||||||
|
size="s12",
|
||||||
|
form_obj=user_form.username
|
||||||
|
) }}
|
||||||
|
|
||||||
|
{{ input(
|
||||||
|
label="Password",
|
||||||
|
id="user-form-password",
|
||||||
|
form_obj=user_form.password,
|
||||||
|
size="s12"
|
||||||
|
) }}
|
||||||
|
|
||||||
|
{{ input(
|
||||||
|
label="Confirm Password",
|
||||||
|
id="user-form-confirm_password",
|
||||||
|
form_obj=user_form.confirm_password,
|
||||||
|
required='required',
|
||||||
|
size="s12"
|
||||||
|
) }}
|
||||||
|
|
||||||
|
{{ user_form.id(class="hide", id="user-form-id") }}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{ button(
|
||||||
|
icon="save",
|
||||||
|
float="left",
|
||||||
|
id="save-user-btn",
|
||||||
|
class="mb-2",
|
||||||
|
data={'url': url_for('settings_system.edit_user')},
|
||||||
|
text="save"
|
||||||
|
) }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col s12">
|
||||||
|
<h5>Users
|
||||||
|
<a href="#user-modal" class="modal-trigger">
|
||||||
|
<i class="material-icons-outlined theme-secondary-text icon-btn ml-2 toggle-config-help" style="position: relative; top: 4px;">add</i>
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div id="users-div">
|
||||||
|
{{ Users(users) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<h5>DashMachine</h5>
|
||||||
|
<p class="mb-2">version: {{ version }}</p>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro Users(users) %}
|
||||||
|
{% for user in users %}
|
||||||
|
<div class="card theme-surface-1">
|
||||||
|
<div class="card-content">
|
||||||
|
<span style="font-size: 1.3rem">
|
||||||
|
{{ user.username }}
|
||||||
|
<span class="theme-secondary-text">{{ user.role }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="right pb-2">
|
||||||
|
<i class="material-icons-outlined icon-btn edit-user-btn"
|
||||||
|
data-role="{{ user.role }}"
|
||||||
|
data-id="{{ user.id }}"
|
||||||
|
data-username="{{ user.username }}">edit</i>
|
||||||
|
<i class="material-icons-outlined icon-btn delete-user-btn"
|
||||||
|
data-id="{{ user.id }}"
|
||||||
|
data-url="{{ url_for('settings_system.delete_user') }}">close</i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<script>
|
||||||
|
$(".edit-user-btn").on('click', function(e) {
|
||||||
|
$("#user-modal").modal('open');
|
||||||
|
$("#user-form-username").val($(this).attr("data-username"));
|
||||||
|
$("#user-form-role").val($(this).attr("data-role"));
|
||||||
|
$("#user-form-id").val($(this).attr("data-id"));
|
||||||
|
M.updateTextFields();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".delete-user-btn").on('click', function(e) {
|
||||||
|
var r = confirm("Are you sure?");
|
||||||
|
if (r == true) {
|
||||||
|
$.ajax({
|
||||||
|
url: $(this).attr('data-url'),
|
||||||
|
type: 'GET',
|
||||||
|
data: {id: $(this).attr('data-id')},
|
||||||
|
success: function(data){
|
||||||
|
if (data.data.err !== 'success') {
|
||||||
|
M.toast({html: data.data.err, classes: 'theme-failure'});
|
||||||
|
} else {
|
||||||
|
$("#users-div").empty();
|
||||||
|
$("#users-div").append(data.data.html);
|
||||||
|
$("#edit-user-modal").modal('close');
|
||||||
|
M.toast({html: 'User deleted'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{{Users(users)}}
|
19
dashmachine/user_system/forms.py
Executable file → Normal file
19
dashmachine/user_system/forms.py
Executable file → Normal file
@ -1,10 +1,9 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import (
|
from wtforms import StringField, PasswordField, BooleanField, SelectField
|
||||||
StringField,
|
|
||||||
PasswordField,
|
|
||||||
BooleanField,
|
|
||||||
)
|
|
||||||
from wtforms.validators import DataRequired, Length
|
from wtforms.validators import DataRequired, Length
|
||||||
|
from dashmachine.settings_system.models import Settings
|
||||||
|
|
||||||
|
settings_db = Settings.query.first()
|
||||||
|
|
||||||
|
|
||||||
class UserForm(FlaskForm):
|
class UserForm(FlaskForm):
|
||||||
@ -12,6 +11,16 @@ class UserForm(FlaskForm):
|
|||||||
|
|
||||||
password = PasswordField(validators=[DataRequired(), Length(min=8, max=120)])
|
password = PasswordField(validators=[DataRequired(), Length(min=8, max=120)])
|
||||||
|
|
||||||
|
role = SelectField(choices=[(role, role) for role in settings_db.roles.split(",")])
|
||||||
|
|
||||||
|
id = StringField()
|
||||||
|
|
||||||
confirm_password = PasswordField()
|
confirm_password = PasswordField()
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(FlaskForm):
|
||||||
|
username = StringField(validators=[DataRequired(), Length(min=1, max=120)])
|
||||||
|
|
||||||
|
password = PasswordField(validators=[DataRequired(), Length(min=8, max=120)])
|
||||||
|
|
||||||
remember = BooleanField()
|
remember = BooleanField()
|
||||||
|
@ -11,7 +11,4 @@ class User(db.Model, UserMixin):
|
|||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
username = db.Column(db.String(120), unique=True, nullable=False)
|
username = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
password = db.Column(db.String(60), nullable=False)
|
password = db.Column(db.String(60), nullable=False)
|
||||||
|
role = db.Column(db.String())
|
||||||
|
|
||||||
db.create_all()
|
|
||||||
db.session.commit()
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from flask import render_template, url_for, redirect, Blueprint
|
from flask import render_template, url_for, redirect, Blueprint
|
||||||
from flask_login import login_user, logout_user, current_user
|
from flask_login import login_user, logout_user
|
||||||
from dashmachine.user_system.forms import UserForm
|
from dashmachine.user_system.forms import LoginForm
|
||||||
from dashmachine.user_system.models import User
|
from dashmachine.user_system.models import User
|
||||||
from dashmachine.user_system.utils import add_edit_user
|
from dashmachine.user_system.utils import add_edit_user
|
||||||
from dashmachine import bcrypt
|
from dashmachine import bcrypt
|
||||||
@ -18,10 +18,7 @@ user_system = Blueprint("user_system", __name__)
|
|||||||
def login():
|
def login():
|
||||||
user = User.query.first()
|
user = User.query.first()
|
||||||
|
|
||||||
if current_user.is_authenticated:
|
form = LoginForm()
|
||||||
return redirect(url_for("main.home"))
|
|
||||||
|
|
||||||
form = UserForm()
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user = User.query.filter_by(username=form.username.data.lower()).first()
|
user = User.query.filter_by(username=form.username.data.lower()).first()
|
||||||
|
@ -2,16 +2,21 @@ from dashmachine import db, bcrypt
|
|||||||
from dashmachine.user_system.models import User
|
from dashmachine.user_system.models import User
|
||||||
|
|
||||||
|
|
||||||
def add_edit_user(username, password, user_id=None):
|
def add_edit_user(username, password, user_id=None, role=None):
|
||||||
if user_id:
|
if user_id:
|
||||||
user = User.query.filter_by(id=user_id).first()
|
user = User.query.filter_by(id=user_id).first()
|
||||||
else:
|
|
||||||
user = User.query.first()
|
|
||||||
if not user:
|
if not user:
|
||||||
user = User()
|
user = User()
|
||||||
|
else:
|
||||||
|
user = User()
|
||||||
|
|
||||||
|
admin_users = User.query.filter_by(role="admin").all()
|
||||||
|
if user_id and role != "admin" and len(admin_users) < 2:
|
||||||
|
return "You must have at least one admin user"
|
||||||
|
|
||||||
hashed_password = bcrypt.generate_password_hash(password).decode("utf-8")
|
hashed_password = bcrypt.generate_password_hash(password).decode("utf-8")
|
||||||
user.username = username
|
user.username = username
|
||||||
user.password = hashed_password
|
user.password = hashed_password
|
||||||
|
user.role = role
|
||||||
db.session.merge(user)
|
db.session.merge(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -1 +1 @@
|
|||||||
version = "v0.22"
|
version = "v0.3"
|
||||||
|
@ -2,3 +2,6 @@
|
|||||||
theme = light
|
theme = light
|
||||||
accent = orange
|
accent = orange
|
||||||
background = None
|
background = None
|
||||||
|
roles = admin,user,public_user
|
||||||
|
home_access_groups = admin_only
|
||||||
|
settings_access_groups = admin_only
|
28
migrations/versions/01a575cda54d_.py
Normal file
28
migrations/versions/01a575cda54d_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 01a575cda54d
|
||||||
|
Revises: 598477dd1193
|
||||||
|
Create Date: 2020-02-04 07:39:43.504475
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "01a575cda54d"
|
||||||
|
down_revision = "598477dd1193"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("apps", sa.Column("groups", sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("apps", "groups")
|
||||||
|
# ### end Alembic commands ###
|
28
migrations/versions/03663c18575b_.py
Normal file
28
migrations/versions/03663c18575b_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 03663c18575b
|
||||||
|
Revises: af72304ae017
|
||||||
|
Create Date: 2020-02-04 07:14:23.184567
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "03663c18575b"
|
||||||
|
down_revision = "af72304ae017"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("user", sa.Column("role", sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("user", "role")
|
||||||
|
# ### end Alembic commands ###
|
40
migrations/versions/45ebff47af9f_.py
Normal file
40
migrations/versions/45ebff47af9f_.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 45ebff47af9f
|
||||||
|
Revises: 6bd40f00f2eb
|
||||||
|
Create Date: 2020-02-06 11:48:22.563926
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "45ebff47af9f"
|
||||||
|
down_revision = "6bd40f00f2eb"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("api_calls")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"api_calls",
|
||||||
|
sa.Column("id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("name", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("resource", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("method", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("payload", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("authentication", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("username", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("password", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("value_template", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
28
migrations/versions/598477dd1193_.py
Normal file
28
migrations/versions/598477dd1193_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 598477dd1193
|
||||||
|
Revises: 03663c18575b
|
||||||
|
Create Date: 2020-02-04 07:33:25.019173
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "598477dd1193"
|
||||||
|
down_revision = "03663c18575b"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("settings", sa.Column("roles", sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("settings", "roles")
|
||||||
|
# ### end Alembic commands ###
|
40
migrations/versions/6bd40f00f2eb_.py
Normal file
40
migrations/versions/6bd40f00f2eb_.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 6bd40f00f2eb
|
||||||
|
Revises: d87e35114b0b
|
||||||
|
Create Date: 2020-02-05 18:41:57.209232
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "6bd40f00f2eb"
|
||||||
|
down_revision = "d87e35114b0b"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("apps", sa.Column("groups", sa.String(), nullable=True))
|
||||||
|
op.add_column(
|
||||||
|
"settings", sa.Column("home_access_groups", sa.String(), nullable=True)
|
||||||
|
)
|
||||||
|
op.add_column("settings", sa.Column("roles", sa.String(), nullable=True))
|
||||||
|
op.add_column(
|
||||||
|
"settings", sa.Column("settings_access_groups", sa.String(), nullable=True)
|
||||||
|
)
|
||||||
|
op.add_column("user", sa.Column("role", sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("user", "role")
|
||||||
|
op.drop_column("settings", "settings_access_groups")
|
||||||
|
op.drop_column("settings", "roles")
|
||||||
|
op.drop_column("settings", "home_access_groups")
|
||||||
|
op.drop_column("apps", "groups")
|
||||||
|
# ### end Alembic commands ###
|
28
migrations/versions/885c5f9b33d5_.py
Normal file
28
migrations/versions/885c5f9b33d5_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 885c5f9b33d5
|
||||||
|
Revises: 8f5a046465e8
|
||||||
|
Create Date: 2020-02-08 13:30:01.632487
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "885c5f9b33d5"
|
||||||
|
down_revision = "8f5a046465e8"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column("apps", sa.Column("tags", sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("apps", "tags")
|
||||||
|
# ### end Alembic commands ###
|
40
migrations/versions/8f5a046465e8_.py
Normal file
40
migrations/versions/8f5a046465e8_.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 8f5a046465e8
|
||||||
|
Revises: 45ebff47af9f
|
||||||
|
Create Date: 2020-02-06 19:51:14.594434
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "8f5a046465e8"
|
||||||
|
down_revision = "45ebff47af9f"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("api_calls")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"api_calls",
|
||||||
|
sa.Column("id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("name", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("resource", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("method", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("payload", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("authentication", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("username", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("password", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column("value_template", sa.VARCHAR(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
34
migrations/versions/d87e35114b0b_.py
Normal file
34
migrations/versions/d87e35114b0b_.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: d87e35114b0b
|
||||||
|
Revises: 01a575cda54d
|
||||||
|
Create Date: 2020-02-04 08:13:35.783741
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "d87e35114b0b"
|
||||||
|
down_revision = "01a575cda54d"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column(
|
||||||
|
"settings", sa.Column("home_access_groups", sa.String(), nullable=True)
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"settings", sa.Column("settings_access_groups", sa.String(), nullable=True)
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("settings", "settings_access_groups")
|
||||||
|
op.drop_column("settings", "home_access_groups")
|
||||||
|
# ### end Alembic commands ###
|
@ -32,3 +32,5 @@ SQLAlchemy==1.3.13
|
|||||||
urllib3==1.25.8
|
urllib3==1.25.8
|
||||||
Werkzeug==0.16.1
|
Werkzeug==0.16.1
|
||||||
WTForms==2.2.1
|
WTForms==2.2.1
|
||||||
|
transmissionrpc
|
||||||
|
markdown2
|
Loading…
x
Reference in New Issue
Block a user