From 102823e3071e985f4c9349b945693657382ba42c Mon Sep 17 00:00:00 2001 From: ball42 Date: Mon, 12 Apr 2021 14:44:35 -0500 Subject: [PATCH 001/168] updated .gitignore --- .gitignore | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/.gitignore b/.gitignore index c0957ad..6d33eb8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,140 @@ security/* # ignore mac .DS_Store .DS_Store + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# OS generated files # +###################### +.DS_Store? +._* +.Spotlight-V100 +.Trashes \ No newline at end of file From e6f20d0543aeae2fde56f5da3f75f7619bff23dd Mon Sep 17 00:00:00 2001 From: ball42 Date: Mon, 12 Apr 2021 14:46:13 -0500 Subject: [PATCH 002/168] added requirements.txt --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..79cefef --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +requests~=2.25.1 +crontab~=0.22.9 +Werkzeug~=1.0.1 +Flask~=1.1.2 +waitress~=2.0.0 \ No newline at end of file From 81584e9823e7effd5590410f39029950540c5336 Mon Sep 17 00:00:00 2001 From: ball42 Date: Mon, 12 Apr 2021 14:46:43 -0500 Subject: [PATCH 003/168] restructured webapp.py w/ register_blueprints(), added environment_setup() and adjusted config files to use relative paths --- webapp.py | 224 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 116 insertions(+), 108 deletions(-) diff --git a/webapp.py b/webapp.py index a866d0c..997b76a 100644 --- a/webapp.py +++ b/webapp.py @@ -1,32 +1,39 @@ #!/usr/bin/python3 # encoding: utf-8 -import sys import os import json -import base64 -import zipfile import glob import logging from time import sleep -from werkzeug.utils import secure_filename -import signal import requests -import re -from crontab import CronTab -from flask import (Flask, request, render_template, - session, redirect, url_for, escape, - send_from_directory, Blueprint, abort) +from flask import (Flask, request, render_template, + session, redirect, url_for, escape) from waitress import serve -from webapp.new_jp_webhook import new_jp -from webapp.edit_jp_webhook import edit_jp -from webapp.delete_jp_webhook import delete_jp -from webapp.new_okta_webhook import new_okta -from webapp.delete_okta_webhook import delete_okta +def register_blueprints(): + # New Jamf Pro Webhook + from webapp.new_jp_webhook import new_jp + app.register_blueprint(new_jp) + # Edit Jamf Pro Webhook + from webapp.edit_jp_webhook import edit_jp + app.register_blueprint(edit_jp) + # Delete Jamf Pro Webhook + from webapp.delete_jp_webhook import delete_jp + app.register_blueprint(delete_jp) + # New Okta Webhook + from webapp.new_okta_webhook import new_okta + app.register_blueprint(new_okta) + # Delete Existing Webhook + from webapp.delete_okta_webhook import delete_okta + app.register_blueprint(delete_okta) + # Create a new Cron Job + from webapp.new_cron_job import new_cron + app.register_blueprint(new_cron) + # Delete a Cron Job + from webapp.delete_cron_job import cron_delete + app.register_blueprint(cron_delete) -from webapp.new_cron_job import new_cron -from webapp.delete_cron_job import cron_delete # Flask logging logger = logging.getLogger('waitress') @@ -37,46 +44,30 @@ global jamf_username global jamf_password global distilled_serial -verify_ssl = True # Enables Jamf Pro SSL certificate verification +verify_ssl = True # Enables Jamf Pro SSL certificate verification # Initiate Flask app = Flask(__name__) -webhook_file = "/etc/webhook.conf" -jp_file = "/usr/local/jawa/jp_webhooks.json" -okta_file = "/usr/local/jawa/okta_json.json" -cron_file = "/usr/local/jawa/cron.json" -server_json_file = '/usr/local/jawa/webapp/server.json' -scripts_directory = "/usr/local/jawa/scripts" +def environment_setup(project_dir): + global webhook_file, jp_file, okta_file, cron_file, server_json_file, scripts_directory + webhook_file = "/etc/webhook.conf" + jp_file = os.path.join(project_dir, 'jp_webhooks.json') + okta_file = os.path.join(project_dir, 'okta_json.json') + cron_file = os.path.join(project_dir, 'cron.json') + server_json_file = os.path.join(os.path.join(project_dir, 'webapp'), 'jp_webhooks.json') + scripts_directory = os.path.join(project_dir, 'scripts') -# New Jamf Pro Webhook -app.register_blueprint(new_jp) -# Edit Jamf Pro Webhook -app.register_blueprint(edit_jp) -# Delete Jamf Pro Webhook -app.register_blueprint(delete_jp) - -# New Okta Webhook -app.register_blueprint(new_okta) - -# Delete Existing Webhook -app.register_blueprint(delete_okta) - -# Create a new Cron Job -app.register_blueprint(new_cron) - -# Delete a Cron Job -app.register_blueprint(cron_delete) # Server setup including making .json file necessary for webhooks -@app.route('/setup', methods=['GET','POST']) +@app.route('/setup', methods=['GET', 'POST']) def setup(): - if 'username' in session: - - global jawa_address + if 'username' in session: + + # global jawa_address if request.method == 'POST': server_url = request.form.get('address') @@ -95,19 +86,19 @@ def setup(): if os.path.isfile(server_json_file): with open(server_json_file) as outfile: data = json.load(outfile) - data[0].update(new_json) + data[0].update(new_json) with open(server_json_file, "w+") as outfile: json.dump(data, outfile) - return render_template('success.html', - webhooks="success", - username=str(escape(session['username']))) + return render_template('success.html', + webhooks="success", + username=str(escape(session['username']))) else: - return render_template('setup.html', - login="false",jps_url=str(escape(session['url']))) + return render_template('setup.html', + login="false", jps_url=str(escape(session['url']))) else: - return render_template('home.html', - login="false") + return render_template('home.html', + login="false") @app.route("/cleanup", methods=['GET', 'POST']) @@ -116,22 +107,23 @@ def cleanup(): if request.method == 'POST': os.chdir(scripts_directory) - + for file in glob.glob("*.old"): os.remove(file) - return render_template('wizard.html', - wizard="wizard", - username=str(escape(session['username']))) - + return render_template('wizard.html', + wizard="wizard", + username=str(escape(session['username']))) + else: return render_template( 'cleanup.html', login="true", username=str(escape(session['username']))) else: - return render_template('home.html', - login="false") + return render_template('home.html', + login="false") + # Login page verifying communication/permissions to Jamf Pro @app.route('/login', methods=['GET', 'POST']) @@ -157,8 +149,8 @@ def login(): if request.form['password'] != "": try: response = requests.get( - session['url'] + '/JSSResource/activationcode', - auth=(session['username'], session['password']), + session['url'] + '/JSSResource/activationcode', + auth=(session['username'], session['password']), headers={'Accept': 'application/json'}, verify=verify_ssl) @@ -175,8 +167,8 @@ def login(): return redirect(url_for('wizard')) else: - return render_template('home.html', - login="failed") + return render_template('home.html', + login="failed") return render_template('home.html', login="true") @@ -187,7 +179,6 @@ def login(): @app.route('/') def index(): - if not os.path.isfile(server_json_file): return render_template('home.html') else: @@ -201,14 +192,15 @@ def index(): elif len(server_json[0]['jps_url']) == 0: return render_template('home.html') else: - session['url']=server_json[0]['jps_url'] - return render_template('home.html', - jps_url=str(escape(session['url'])), - welcome="true", jsslock="true") - + session['url'] = server_json[0]['jps_url'] + return render_template('home.html', + jps_url=str(escape(session['url'])), + welcome="true", jsslock="true") + session.pop('username', None) return render_template('home.html', login="true") + @app.route("/") def home(): if 'username' in session: @@ -225,32 +217,32 @@ def home(): elif len(server_json[0]['jps_url']) == 0: return render_template('home.html') else: - session['url']=server_json[0]['jps_url'] - return render_template('home.html', - jps_url=str(escape(session['url'])), - welcome="true", jsslock="true") - + session['url'] = server_json[0]['jps_url'] + return render_template('home.html', + jps_url=str(escape(session['url'])), + welcome="true", jsslock="true") + session.pop('username', None) return render_template('home.html') + @app.route("/wizard") def wizard(): if not os.path.isfile(webhook_file): return redirect(url_for('setup')) - with open(jp_file) as webhook_json: + with open(jp_file) as webhook_json: webhooks_installed = json.load(webhook_json) webhook_json = [] response = requests.get( session['url'] + '/JSSResource/webhooks', - auth=(session['username'], session['password']), + auth=(session['username'], session['password']), headers={'Accept': 'application/json'}, verify=verify_ssl) found_jamf_webhooks = response.json()['webhooks'] - x = 0 jamf_webhooks = [] while True: @@ -270,8 +262,8 @@ def wizard(): if webhook['name'] in jamf_webhooks: webhook_endpoint = '/JSSResource/webhooks/name/' response = requests.get( - session['url'] + webhook_endpoint + webhook['name'], - auth=(session['username'], session['password']), + session['url'] + webhook_endpoint + webhook['name'], + auth=(session['username'], session['password']), headers={'Accept': 'application/json'}, verify=verify_ssl) @@ -282,16 +274,16 @@ def wizard(): script = webhook['script'].rsplit('/', 1) webhook_json.append({"name": webhook['name'], - "jamf_id": jamf_id, - "event": jamf_event, - "script": script[1], - "description": webhook['description']}) + "jamf_id": jamf_id, + "event": jamf_event, + "script": script[1], + "description": webhook['description']}) data = [] if not os.path.isfile(cron_file): with open(cron_file, "w") as outfile: - json.dump(data, outfile) + json.dump(data, outfile) with open(cron_file) as cron_json: crons_installed = json.load(cron_json) @@ -299,15 +291,15 @@ def wizard(): for cron in crons_installed: script = cron['script'].rsplit('/', 1) crons_json.append({"name": cron['name'], - "frequency": cron['frequency'], - "script": script[1], - "description": cron['description']}) + "frequency": cron['frequency'], + "script": script[1], + "description": cron['description']}) data = [] if not os.path.isfile(okta_file): with open(okta_file, "w") as outfile: - json.dump(data, outfile) + json.dump(data, outfile) with open(okta_file) as okta_json: oktas_installed = json.load(okta_json) @@ -315,8 +307,8 @@ def wizard(): for okta in oktas_installed: script = okta['script'].rsplit('/', 1) oktas_json.append({"name": okta['name'], - "event": okta['okta_event'], - "script": script[1]}) + "event": okta['okta_event'], + "script": script[1]}) webhook_url = session['url'] @@ -339,11 +331,12 @@ def wizard(): login="true", username=str(escape(session['username']))) + @app.route("/first_automation") def first_automation(): if not os.path.isfile(webhook_file): return redirect(url_for('setup')) - + if 'username' in session: return render_template( 'first_automation.html', @@ -356,21 +349,23 @@ def step_one(): if request.method == 'POST': if request.form['webhook_source'] == 'jamf': return redirect(url_for('webhooks.webhooks')) - + if request.form['webhook_source'] == 'okta': return redirect(url_for('okta_new.okta_new')) - + return render_template( 'step_one.html', login="true", username=str(escape(session['username']))) + @app.route("/python") def python(): return render_template( - 'python.html', + 'python.html', login="true", - username=str(escape(session['username']))) + username=str(escape(session['username']))) + @app.route("/bash") def bash(): @@ -379,7 +374,8 @@ def bash(): login="true", username=str(escape(session['username']))) -@app.route('/success', methods=['GET','POST']) + +@app.route('/success', methods=['GET', 'POST']) def success(): if 'username' in session: return render_template( @@ -387,21 +383,24 @@ def success(): login="true", username=str(escape(session['username']))) -@app.route('/error', methods=['GET','POST']) + +@app.route('/error', methods=['GET', 'POST']) def error(): if 'username' in session: return render_template('home.html', login="false") + @app.errorhandler(404) def page_not_found(error): if 'username' in session: - return render_template('home.html', - error="true", - username=str(escape(session['username']))), 404 - - return render_template('home.html', - error="true", - login="true"), 404 + return render_template('home.html', + error="true", + username=str(escape(session['username']))), 404 + + return render_template('home.html', + error="true", + login="true"), 404 + @app.route('/logout') def logout(): @@ -410,6 +409,15 @@ def logout(): return redirect(url_for('index')) -if __name__ == '__main__': + +def main(): + base_dir = os.path.dirname(__file__) + print(base_dir) + environment_setup(base_dir) + register_blueprints() app.secret_key = "*" - serve(app, url_scheme='https',host='0.0.0.0', port=8000) \ No newline at end of file + serve(app, url_scheme='https', host='0.0.0.0', port=8000) + + +if __name__ == '__main__': + main() From e909ee1eeac5cb7713c13b7cc9bcc9d1d834b6a3 Mon Sep 17 00:00:00 2001 From: ball42 Date: Tue, 27 Apr 2021 13:42:14 -0500 Subject: [PATCH 004/168] restructured dir tree, py3 updates for okta_verification.py --- .../okta_verification.py | 2 +- cron.json => data/cron.json | 0 data/jp_webhooks.json | 1 + okta_json.json => data/okta_json.json | 0 time.json => data/time.json | 0 jp_webhooks.json | 1 - webapp.py | 80 ++++++++++--------- 7 files changed, 43 insertions(+), 41 deletions(-) rename okta_verification.py => bin/okta_verification.py (80%) rename cron.json => data/cron.json (100%) create mode 100644 data/jp_webhooks.json rename okta_json.json => data/okta_json.json (100%) rename time.json => data/time.json (100%) delete mode 100644 jp_webhooks.json diff --git a/okta_verification.py b/bin/okta_verification.py similarity index 80% rename from okta_verification.py rename to bin/okta_verification.py index d746695..a811661 100755 --- a/okta_verification.py +++ b/bin/okta_verification.py @@ -7,4 +7,4 @@ json_text = {"verification": "{}".format(challenge)} -print json.dumps(json_text) +print(json.dumps(json_text)) diff --git a/cron.json b/data/cron.json similarity index 100% rename from cron.json rename to data/cron.json diff --git a/data/jp_webhooks.json b/data/jp_webhooks.json new file mode 100644 index 0000000..0e6f2cb --- /dev/null +++ b/data/jp_webhooks.json @@ -0,0 +1 @@ +[{"jawa_address": "http://turnip.ball.farm:4242", "jps_url": "https://shinra.jamfcloud.com"}] \ No newline at end of file diff --git a/okta_json.json b/data/okta_json.json similarity index 100% rename from okta_json.json rename to data/okta_json.json diff --git a/time.json b/data/time.json similarity index 100% rename from time.json rename to data/time.json diff --git a/jp_webhooks.json b/jp_webhooks.json deleted file mode 100644 index 0637a08..0000000 --- a/jp_webhooks.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/webapp.py b/webapp.py index 997b76a..cc7af16 100644 --- a/webapp.py +++ b/webapp.py @@ -10,6 +10,40 @@ session, redirect, url_for, escape) from waitress import serve +# Flask logging +logger = logging.getLogger('waitress') +logger.setLevel(logging.INFO) +error_message = "" +verify_ssl = True # Enables Jamf Pro SSL certificate verification + +# global jamf_url +# global jamf_username +# global jamf_password +# global distilled_serial +app = Flask(__name__) + +# Initiate Flask + + +def main(): + base_dir = os.path.dirname(__file__) + print(base_dir) + environment_setup(base_dir) + register_blueprints() + app.secret_key = "*" + serve(app, url_scheme='http', host='0.0.0.0', port=8000) + + +def environment_setup(project_dir): + global webhook_file, jp_file, okta_file, cron_file, server_json_file, scripts_directory + webhook_file = "/etc/webhook.conf" + jp_file = os.path.join(project_dir, 'data/jp_webhooks.json') + okta_file = os.path.join(project_dir, 'data/okta_json.json') + cron_file = os.path.join(project_dir, 'data/cron.json') + server_json_file = os.path.join(os.path.join(project_dir, 'webapp'), 'data/jp_webhooks.json') + scripts_directory = os.path.join(project_dir, 'scripts') + # return webhook_file, jp_file, okta_file, cron_file, server_json_file, scripts_directory + def register_blueprints(): # New Jamf Pro Webhook @@ -35,34 +69,9 @@ def register_blueprints(): app.register_blueprint(cron_delete) -# Flask logging -logger = logging.getLogger('waitress') -logger.setLevel(logging.INFO) -error_message = "" - -global jamf_url -global jamf_username -global jamf_password -global distilled_serial -verify_ssl = True # Enables Jamf Pro SSL certificate verification - -# Initiate Flask -app = Flask(__name__) - - -def environment_setup(project_dir): - global webhook_file, jp_file, okta_file, cron_file, server_json_file, scripts_directory - webhook_file = "/etc/webhook.conf" - jp_file = os.path.join(project_dir, 'jp_webhooks.json') - okta_file = os.path.join(project_dir, 'okta_json.json') - cron_file = os.path.join(project_dir, 'cron.json') - server_json_file = os.path.join(os.path.join(project_dir, 'webapp'), 'jp_webhooks.json') - scripts_directory = os.path.join(project_dir, 'scripts') - - +# Server setup including making .json file necessary for webhooks -# Server setup including making .json file necessary for webhooks @app.route('/setup', methods=['GET', 'POST']) def setup(): if 'username' in session: @@ -126,6 +135,12 @@ def cleanup(): # Login page verifying communication/permissions to Jamf Pro + + +######################### +# General Webapp Settings +######################### + @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': @@ -173,10 +188,6 @@ def login(): return render_template('home.html', login="true") -######################### -# General Webapp Settings -######################### - @app.route('/') def index(): if not os.path.isfile(server_json_file): @@ -410,14 +421,5 @@ def logout(): return redirect(url_for('index')) -def main(): - base_dir = os.path.dirname(__file__) - print(base_dir) - environment_setup(base_dir) - register_blueprints() - app.secret_key = "*" - serve(app, url_scheme='https', host='0.0.0.0', port=8000) - - if __name__ == '__main__': main() From c8378c43619855e012b5b19b4f74fb8418e3f9f5 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 29 Apr 2021 00:42:13 -0500 Subject: [PATCH 005/168] Gatekeeping (gitkeeping?) our scripts dir --- scripts/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 scripts/.gitkeep diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 From 5977cbe1ccb17230112f69fcdf125e448ce8b803 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 29 Apr 2021 00:43:58 -0500 Subject: [PATCH 006/168] Added webhook validation with jp_webhooks.json, auth check, & script execution --- webhook/jawa_receiver.py | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 webhook/jawa_receiver.py diff --git a/webhook/jawa_receiver.py b/webhook/jawa_receiver.py new file mode 100644 index 0000000..a24c4ce --- /dev/null +++ b/webhook/jawa_receiver.py @@ -0,0 +1,62 @@ +import flask +from flask import session, request, redirect, url_for, render_template +from glob import escape +import json +import logging +import os +import requests +import subprocess + + +server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) +webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) +scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) + +blueprint = flask.Blueprint('jawa_receiver', __name__, template_folder='templates') +logger = logging.getLogger('waitress') + + +def validate_webhook(webhook_data, webhook_name): + with open(jp_webhooks_file, "r") as fin: + webhooks_json = json.load(fin) + truth_test = False + for each_webhook in webhooks_json: + if each_webhook['name'] == webhook_name: + truth_test = True + return truth_test + + + +def run_script(webhook_data, webhook_name): + with open(jp_webhooks_file, "r") as fin: + webhooks_json = json.load(fin) + for each_webhook in webhooks_json: + if each_webhook['name'] == webhook_name: + subprocess.Popen([each_webhook['script'], f"{webhook_data}"]) + + +@blueprint.route('/hooks/', methods=['POST']) +def jamf_webhook_handler(webhook_name): + print(webhook_name) + webhook_data = request.get_json() + auth = request.authorization + webhook_user = "null" + webhook_pass = "null" + if auth: # .get("username"): + webhook_user = auth.get("username") + webhook_pass = auth.get("password") + print(f"Webhook user: {webhook_user}\n" # todo: check username from new webhook.conf + f"Webhook password: {webhook_pass}\n" # todo: check password from new webhook.conf + f"Webhook name: {webhook_name}\n" + f"Webhook data: {webhook_data}") + + print(webhook_data) + print(webhook_name) + if validate_webhook(webhook_data, webhook_name): + print(f"{webhook_name} validated!") + run_script(webhook_data, webhook_name) + else: + print(f"{webhook_name} not validated!") + + return webhook_data From 106a9910cbe0569ac29ec7255ede9fb08c1d7f64 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 29 Apr 2021 00:45:35 -0500 Subject: [PATCH 007/168] re-priming configs --- data/jp_webhooks.json | 2 +- data/server.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 data/server.json diff --git a/data/jp_webhooks.json b/data/jp_webhooks.json index 0e6f2cb..0637a08 100644 --- a/data/jp_webhooks.json +++ b/data/jp_webhooks.json @@ -1 +1 @@ -[{"jawa_address": "http://turnip.ball.farm:4242", "jps_url": "https://shinra.jamfcloud.com"}] \ No newline at end of file +[] \ No newline at end of file diff --git a/data/server.json b/data/server.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/data/server.json @@ -0,0 +1 @@ +[] \ No newline at end of file From 11aeeb5b89a8ac3a0b2483e35d170879e074e204 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 29 Apr 2021 00:46:22 -0500 Subject: [PATCH 008/168] + deprecated webhook.conf, using jp_webhooks.json instead + relative paths for OS agnosticism --- webapp/delete_jp_webhook.py | 165 +++++++------- webapp/edit_jp_webhook.py | 408 ++++++++++++++++++----------------- webapp/new_jp_webhook.py | 418 +++++++++++++++++++----------------- 3 files changed, 515 insertions(+), 476 deletions(-) diff --git a/webapp/delete_jp_webhook.py b/webapp/delete_jp_webhook.py index 91c9b31..04ec4e7 100644 --- a/webapp/delete_jp_webhook.py +++ b/webapp/delete_jp_webhook.py @@ -9,84 +9,99 @@ import requests import re from werkzeug.utils import secure_filename -from flask import (Flask, request, render_template, - session, redirect, url_for, escape, - send_from_directory, Blueprint, abort) +from flask import (Flask, request, render_template, + session, redirect, url_for, escape, + send_from_directory, Blueprint, abort) verify_ssl = True +server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) +webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) +scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) + delete_jp = Blueprint('delete', __name__) -@delete_jp.route('/delete', methods=['GET','POST']) + +@delete_jp.route('/delete', methods=['GET', 'POST']) def delete(): - exists = os.path.isfile('/usr/local/jawa/webapp/server.json') - if exists == False: - return render_template('setup.html', - setup="setup", - username=str(escape(session['username']))) - - if 'username' in session: - text = open('/usr/local/jawa/jp_webhooks.json', 'r+') - content = text.read() - webhook_data = json.loads(content) - i = 0 - names = [] - for item in webhook_data: - names.append(str(webhook_data[i]['name'])) - i += 1 - - content = names - - if request.method == 'POST': - if request.form.get('webhookname') != '': - if ' ' in request.form.get('webhookname'): - error_message = "Single-string name only." - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - webhookname = request.form.get('webhookname') - ts = time.time() - - data = '' - data += '' - data += '{}.old_{}'.format(webhookname, ts) - data += 'false' - full_url = session['url'] + '/JSSResource/webhooks/name/' + webhookname - response = requests.put(full_url, - auth=(session['username'], session['password']), - headers={'Content-Type': 'application/xml'}, - verify=verify_ssl, data=data) - - hooks_file = '/etc/webhook.conf' - data = json.load(open(hooks_file)) - - for d in data : - if d['id'] == webhookname: - scriptPath=(d['execute-command']) - newScriptPath = scriptPath + '.old' - os.rename(scriptPath, newScriptPath) - - data[:] = [d for d in data if d.get('id') != webhookname ] - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) - - hooks_file = '/usr/local/jawa/jp_webhooks.json' - data = json.load(open(hooks_file)) - data[:] = [d for d in data if d.get('name') != webhookname ] - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) - - - return redirect(url_for('success')) - - else: - return render_template('delete.html', - text=content, delete="delete", - username=str(escape(session['username']))) - else: - return render_template('home.html', - login="false") + jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) + if not os.path.isfile(server_json_file): + return render_template('setup.html', + setup="setup", + username=str(escape(session['username']))) + + with open(server_json_file, "r") as fin: + server_json = json.load(fin) + if not server_json: + return render_template('setup.html', + setup="setup", + jps_url=str(escape(session['url'])), + username=str(escape(session['username']))) + if 'username' in session: + with open(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')), 'r+') as fin: + # content = fin.read() + webhook_data = json.load(fin) + # text = open(jp_webhooks_file, 'r+') + # content = text.read() + i = 0 + names = [] + for item in webhook_data: + names.append(str(webhook_data[i]['name'])) + i += 1 + + content = names + + if request.method == 'POST': + if request.form.get('webhookname') != '': + if ' ' in request.form.get('webhookname'): + error_message = "Single-string name only." + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + webhookname = request.form.get('webhookname') + ts = time.time() + + data = '' + data += '' + data += '{}.old_{}'.format(webhookname, ts) + data += 'false' + full_url = session['url'] + '/JSSResource/webhooks/name/' + webhookname + response = requests.put(full_url, + auth=(session['username'], session['password']), + headers={'Content-Type': 'application/xml'}, + verify=verify_ssl, data=data) + + with open(jp_webhooks_file, "r") as fin: + data = json.load(fin) + # data = json.load(open(jp_webhooks_file)) + + for d in data: + if d['name'] == webhookname: + scriptPath = (d['script']) + newScriptPath = scriptPath + '.old' + os.rename(scriptPath, newScriptPath) + + data[:] = [d for d in data if d.get('id') != webhookname] + + with open(jp_webhooks_file, 'w') as outfile: + json.dump(data, outfile) + + jp_webhooks_file = jp_webhooks_file + data = json.load(open(jp_webhooks_file)) + data[:] = [d for d in data if d.get('name') != webhookname] + + with open(jp_webhooks_file, 'w') as outfile: + json.dump(data, outfile) + + return redirect(url_for('success')) + + else: + return render_template('delete.html', + text=content, delete="delete", + username=str(escape(session['username']))) + else: + return render_template('home.html', + login="false") diff --git a/webapp/edit_jp_webhook.py b/webapp/edit_jp_webhook.py index e2c7c3b..2e15079 100644 --- a/webapp/edit_jp_webhook.py +++ b/webapp/edit_jp_webhook.py @@ -6,207 +6,217 @@ import requests import re from werkzeug.utils import secure_filename -from flask import (Flask, request, render_template, - session, redirect, url_for, escape, - send_from_directory, Blueprint, abort) +from flask import (Flask, request, render_template, + session, redirect, url_for, escape, + send_from_directory, Blueprint, abort) + +server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) +webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) +scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) edit_jp = Blueprint('edit', __name__) verify_ssl = True + + # Edit Existing Webhook -@edit_jp.route('/edit', methods=['GET','POST']) +@edit_jp.route('/edit', methods=['GET', 'POST']) def edit(): - exists = os.path.isfile('/usr/local/jawa/webapp/server.json') - if exists == False: - return render_template('setup.html', - setup="setup", - jps_url=str(escape(session['url'])), - username=str(escape(session['username']))) - - if 'username' in session: - text = open('/usr/local/jawa/jp_webhooks.json', 'r+') - content = text.read() - webhook_data = json.loads(content) - - i = 0 - names = [] - for item in webhook_data: - names.append(str(webhook_data[i]['name'])) - i += 1 - - content = names - - if request.method == 'POST': - if request.form.get('webhookname') != '': - if ' ' in request.form.get('webhookname'): - error_message = "Single-string name only." - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - check = 0 - - with open('/etc/webhook.conf') as json_file: - data = json.load(json_file) - - x = 0 - id_list = [] - - while True: - try: - id_list.append(data[x]['id']) - x += 1 - str_error = None - except Exception as str_error: - pass - if str_error: - sleep(2) - break - else: - continue - - for id_name in id_list: - if id_name == request.form.get('new_webhookname'): - check = 1 - else: - check = 0 - - if check == 1: - error_message = "Name already exists!" - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - with open('/usr/local/jawa/webapp/server.json') as json_file: - data = json.load(json_file) - server_address = data[0]['jawa_address'] - - if not os.path.isdir('/usr/local/jawa/'): - os.mkdir('/usr/local/jawa/') - - if not os.path.isdir('/usr/local/jawa/scripts'): - os.mkdir('/usr/local/jawa/scripts') - - os.chdir('/usr/local/jawa/scripts') - - f = request.files['script'] - if ' ' in f.filename: - f.filename = f.filename.replace(" ", "-") - - f.save(secure_filename(f.filename)) - old_script_file = "/usr/local/jawa/scripts/{}".format(f.filename) - if request.form.get('new_webhookname') == '': - new_script_file = "/usr/local/jawa/scripts/{}-{}".format(request.form.get('webhookname'), f.filename) - else: - new_script_file = "/usr/local/jawa/scripts/{}-{}".format(request.form.get('new_webhookname'), f.filename) - os.rename(old_script_file, new_script_file) - script_file = new_script_file - hooks_file = '/etc/webhook.conf' - - os.chmod(script_file, mode=0o0755) - - with open(hooks_file) as json_file: - data = json.load(json_file) - - for x in data: - if request.form.get('webhookname') in x['id']: - x['execute-command'] = str(script_file) - if request.form.get('new_webhookname') != '': - x['id'] = str(request.form.get('new_webhookname')) - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) - - jp_hooks_file = '/usr/local/jawa/jp_webhooks.json' - - with open(jp_hooks_file) as json_file: - data = json.load(json_file) - - for x in data: - if request.form.get('webhookname') in x['name']: - x['script'] = str(script_file) - if request.form.get('new_webhookname') != '': - x['name'] = str(request.form.get('new_webhookname')) - - with open(jp_hooks_file, 'w') as outfile: - json.dump(data, outfile) - - - webhookname = request.form.get('webhookname') - - new_webhookname = webhookname - with open('/usr/local/jawa/webapp/server.json') as json_file: - data = json.load(json_file) - server_address = data[0]['jawa_address'] - - add_name = '' - if request.form.get('new_webhookname') != "": - new_webhookname = request.form.get('new_webhookname') - add_name = '{}'.format(new_webhookname) - add_name += '{}/hooks/{}'.format( - server_address, new_webhookname) - - add_event = '' - if request.form.get('event') != 'unchanged': - new_event = request.form.get('event') - add_event = '{}'.format(new_event) - - if ( - request.form.get('event') == 'SmartGroupMobileDeviceMembershipChange' or - request.form.get('event') == 'SmartGroupComputerMembershipChange'): - - smart_group_notice = "NOTICE! This webhook is not yet enabled." - smart_group_instructions = "Specify desired Smart Group and enable: " - webhook_enablement = 'false' - - else: - smart_group_notice = "" - smart_group_instructions = "" - webhook_enablement = 'true' - - else: - smart_group_notice = "" - smart_group_instructions = "" - webhook_enablement = 'true' - - if add_name == '' and add_event == '': - print("No Jamf Change Needed") - smart_group_instructions = "" - smart_group_notice = "" - new_link = "" - new_here = "" - - else: - data = '' - data += add_name - data += '' + webhook_enablement + '' - data += add_event - data += '' - full_url = session['url'] + '/JSSResource/webhooks/name/' + webhookname - response = requests.put(full_url, - auth=(session['username'], session['password']), - headers={'Content-Type': 'application/xml'}, - verify = verify_ssl, - data=data) - result = re.search('(.*)', response.text) - new_link = "{}/webhooks.html?id={}".format(session['url'],result.group(1)) - new_here = "Link" - - return render_template('success.html', - webhooks="success", - smart_group_instructions=smart_group_instructions, - smart_group_notice=smart_group_notice, - new_link=new_link, - new_here=new_here, - username=str(escape(session['username']))) - - else: - return render_template('edit.html', - text=content, - edit="edit", - username=str(escape(session['username']))) - - else: - return render_template('home.html', - login="false") + if not os.path.isfile(server_json_file): + return render_template('setup.html', + setup="setup", + jps_url=str(escape(session['url'])), + username=str(escape(session['username']))) + with open(server_json_file, "r") as fin: + server_json = json.load(fin) + if not server_json: + return render_template('setup.html', + setup="setup", + jps_url=str(escape(session['url'])), + username=str(escape(session['username']))) + if 'username' in session: + with open(jp_webhooks_file, 'r+') as text: + content = text.read() + webhook_data = json.loads(content) + + i = 0 + names = [] + for item in webhook_data: + names.append(str(webhook_data[i]['name'])) + i += 1 + + content = names + + if request.method == 'POST': + if request.form.get('webhookname') != '': + if ' ' in request.form.get('webhookname'): + error_message = "Single-string name only." + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + check = 0 + + with open(webhook_conf) as json_file: + data = json.load(json_file) + + x = 0 + id_list = [] + + while True: + try: + id_list.append(data[x]['id']) + x += 1 + str_error = None + except Exception as str_error: + pass + if str_error: + sleep(2) + break + else: + continue + + for id_name in id_list: + if id_name == request.form.get('new_webhookname'): + check = 1 + else: + check = 0 + + if check == 1: + error_message = "Name already exists!" + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + with open(server_json_file) as json_file: + data = json.load(json_file) + server_address = data[0]['jawa_address'] + + if not os.path.isdir(scripts_dir): + os.mkdir(scripts_dir) + os.chdir(scripts_dir) + + f = request.files['script'] + if ' ' in f.filename: + f.filename = f.filename.replace(" ", "-") + + f.save(secure_filename(f.filename)) + old_script_file = os.path.join(scripts_dir, f.filename) + if request.form.get('new_webhookname') == '': + new_script_file = os.path.join(scripts_dir, f"{request.form.get('webhookname')}-{f.filename}") + else: + new_script_file = os.path.join(scripts_dir, f"{request.form.get('new_webhookname')}-{f.filename}") + + os.rename(old_script_file, new_script_file) + script_file = new_script_file + # webhook_conf = webhook_conf + + os.chmod(script_file, mode=0o0755) + + with open(webhook_conf) as json_file: + data = json.load(json_file) + + for x in data: + if request.form.get('webhookname') in x['id']: + x['execute-command'] = str(script_file) + if request.form.get('new_webhookname') != '': + x['id'] = str(request.form.get('new_webhookname')) + x['event'] = str(request.form.get('event')) + # if request.form.get('event') != '': + # pass + + with open(webhook_conf, 'w') as outfile: + json.dump(data, outfile, indent=4) + + with open(jp_webhooks_file) as json_file: + data = json.load(json_file) + + for x in data: + if request.form.get('webhookname') in x['name']: + x['script'] = str(script_file) + x['event'] = str(request.form.get('event')) + if request.form.get('new_webhookname') != '': + x['name'] = str(request.form.get('new_webhookname')) + + with open(jp_webhooks_file, 'w') as outfile: + json.dump(data, outfile, indent=4) + + webhookname = request.form.get('webhookname') + + new_webhookname = webhookname + with open(server_json_file) as json_file: + data = json.load(json_file) + server_address = data[0]['jawa_address'] + + add_name = '' + if request.form.get('new_webhookname') != "": + new_webhookname = request.form.get('new_webhookname') + add_name = '{}'.format(new_webhookname) + add_name += '{}/hooks/{}'.format( + server_address, new_webhookname) + + add_event = '' + if request.form.get('event') != 'unchanged': + new_event = request.form.get('event') + add_event = '{}'.format(new_event) + + if ( + request.form.get('event') == 'SmartGroupMobileDeviceMembershipChange' or + request.form.get('event') == 'SmartGroupComputerMembershipChange'): + + smart_group_notice = "NOTICE! This webhook is not yet enabled." + smart_group_instructions = "Specify desired Smart Group and enable: " + webhook_enablement = 'false' + + else: + smart_group_notice = "" + smart_group_instructions = "" + webhook_enablement = 'true' + + else: + smart_group_notice = "" + smart_group_instructions = "" + webhook_enablement = 'true' + + if add_name == '' and add_event == '': + print("No Jamf Change Needed") + smart_group_instructions = "" + smart_group_notice = "" + new_link = "" + new_here = "" + + else: + data = '' + data += add_name + data += '' + webhook_enablement + '' + data += add_event + data += '' + full_url = session['url'] + '/JSSResource/webhooks/name/' + webhookname + response = requests.put(full_url, + auth=(session['username'], session['password']), + headers={'Content-Type': 'application/xml'}, + verify=verify_ssl, + data=data) + result = re.search('(.*)', response.text) + new_link = "{}/webhooks.html?id={}".format(session['url'], result.group(1)) + new_here = "Link" + + return render_template('success.html', + webhooks="success", + smart_group_instructions=smart_group_instructions, + smart_group_notice=smart_group_notice, + new_link=new_link, + new_here=new_here, + username=str(escape(session['username']))) + + else: + return render_template('edit.html', + text=content, + edit="edit", + username=str(escape(session['username']))) + + else: + return render_template('home.html', + login="false") diff --git a/webapp/new_jp_webhook.py b/webapp/new_jp_webhook.py index 52c250d..66b537d 100644 --- a/webapp/new_jp_webhook.py +++ b/webapp/new_jp_webhook.py @@ -7,209 +7,223 @@ import requests import re from werkzeug.utils import secure_filename -from flask import (Flask, request, render_template, - session, redirect, url_for, escape, - send_from_directory, Blueprint, abort) +from flask import (Flask, request, render_template, + session, redirect, url_for, escape, + send_from_directory, Blueprint, abort) + +webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) +server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) +scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) +print(server_json_file, jp_webhooks_file) verify_ssl = True new_jp = Blueprint('webhooks', __name__) -@new_jp.route('/webhooks', methods=['GET','POST']) + +@new_jp.route('/webhooks', methods=['GET', 'POST']) def webhooks(): - exists = os.path.isfile('/usr/local/jawa/webapp/server.json') - if exists == False: - return render_template('setup.html', - setup="setup", - jps_url=str(escape(session['url'])), - username=str(escape(session['username']))) - exists = os.path.isfile('/usr/local/jawa/jpwebhooks.json') - if exists == False: - data = [] - with open('/usr/local/jawa/jpwebhooks.json', 'w') as outfile: - json.dump(data, outfile) - - if 'username' in session: - - # response = requests.get(session['url'] + '/JSSResource/computergroups', - # auth=(session['username'], session['password']), - # headers={'Accept': 'application/json'}) - - # response_json = response.json() - - # computer_groups = response_json['computer_groups'] - # found_computer_groups = [] - # for computer_group in computer_groups: - # if computer_group['is_smart'] is True: - # found_computer_groups.append(computer_group) - - # print found_computer_groups - - # response = requests.get(session['url'] + '/JSSResource/mobiledevicegroups', - # auth=(session['username'], session['password']), - # headers={'Accept': 'application/json'}) - - # response_json = response.json() - - # mobile_device_groups = response_json['mobile_device_groups'] - # found_mobile_device_groups = [] - # for mobile_device_group in mobile_device_groups: - # if mobile_device_group['is_smart'] is True: - # found_mobile_device_groups.append(mobile_device_group) - - # print found_mobile_device_groups - - - if request.method == 'POST': - if request.form.get('webhookname') != '': - check = 0 - if ' ' in request.form.get('webhookname'): - error_message = "Single-string name only." - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - with open('/etc/webhook.conf') as json_file: - data = json.load(json_file) - - x = 0 - id_list = [] - while True: - try: - id_list.append(data[x]['id']) - x += 1 - str_error = None - except Exception as str_error: - pass - if str_error: - sleep(2) - break - else: - continue - - for id_name in id_list: - if id_name == request.form.get('webhookname'): - check = 1 - else: - check = 0 - - if check is not 0: - error_message = "Name already exists!" - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - with open('/usr/local/jawa/webapp/server.json') as json_file: - data = json.load(json_file) - server_address = data[0]['jawa_address'] - - if not os.path.isdir('/usr/local/jawa/'): - os.mkdir('/usr/local/jawa/') - if not os.path.isdir('/usr/local/jawa/scripts'): - os.mkdir('/usr/local/jawa/scripts') - - os.chdir('/usr/local/jawa/scripts') - - f = request.files['script'] - if ' ' in f.filename: - f.filename = f.filename.replace(" ", "-") - - f.save(secure_filename(f.filename)) - - old_script_file = "/usr/local/jawa/scripts/{}".format(f.filename) - new_script_file = "/usr/local/jawa/scripts/{}-{}".format(request.form.get('webhookname'), f.filename) - os.rename(old_script_file, new_script_file) - hooks_file = '/etc/webhook.conf' - jp_hooks = '/usr/local/jawa/jp_webhooks.json' - data = json.load(open(hooks_file)) - - new_id = request.form.get('new_webhookname') - - os.chmod(new_script_file, mode=0o0755) - - if type(data) is dict: - data = [data] - - data.append({"id": request.form.get('webhookname'), - "execute-command": new_script_file, - "command-working-directory": "/", - "pass-arguments-to-command":[{"source": "entire-payload"}]}) - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) - - hooks_file = '/etc/webhook.conf' - data = json.load(open(hooks_file)) - - data[:] = [d for d in data if d.get('id') != 'none' ] - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) - - if ( - request.form.get('event') == 'SmartGroupMobileDeviceMembershipChange' or - request.form.get('event') == 'SmartGroupComputerMembershipChange'): - - smart_group_notice = "NOTICE! This webhook is not yet enabled." - smart_group_instructions = "Specify desired Smart Group and enable: " - webhook_enablement = 'false' - - else: - smart_group_notice = "" - smart_group_instructions = "" - webhook_enablement = 'true' - data = '' - data += '' - data += request.form.get('webhookname') - data += '' + webhook_enablement + '' - data += "{}/hooks/{}".format(server_address, request.form.get('webhookname')) - data += 'application/json' - data += '{}'.format(request.form.get('event')) - data += '' - full_url = session['url'] + '/JSSResource/webhooks/id/0' - - response = requests.post(full_url, - auth=(session['username'], session['password']), - headers={'Content-Type': 'application/xml'}, data=data, - verify=verify_ssl) - - result = re.search('(.*)', response.text) - new_link = "{}/webhooks.html?id={}".format(session['url'],result.group(1)) - - data = json.load(open('/usr/local/jawa/jp_webhooks.json')) - - data.append({"url": str(session['url']), - "username": str(session['username']), - "name": request.form.get('webhookname'), - "event": request.form.get('event'), - "script": new_script_file, - "description": request.form.get('description')}) - - with open('/usr/local/jawa/jp_webhooks.json', 'w') as outfile: - json.dump(data, outfile) - - - - new_here = "Link" - new_webhook = "New webhook created." - - return render_template('success.html', - webhooks="success", - smart_group_instructions=smart_group_instructions, - smart_group_notice=smart_group_notice, - new_link=new_link, - new_here=new_here, - new_webhook=new_webhook, - username=str(escape(session['username']))) - - - else: - return render_template('webhooks.html', - webhooks="webhooks", - url=session['url'], - # found_mobile_device_groups=found_mobile_device_groups, - # found_computer_groups=found_computer_groups, - username=str(escape(session['username']))) - - else: - return render_template('home.html', login="false") + if not os.path.isfile(server_json_file): + return render_template('setup.html', + setup="setup", + jps_url=str(escape(session['url'])), + username=str(escape(session['username']))) + with open(server_json_file, "r") as fin: + server_json = json.load(fin) + if server_json == []: + return render_template('setup.html', + setup="setup", + jps_url=str(escape(session['url'])), + username=str(escape(session['username']))) + if not os.path.isfile(jp_webhooks_file): + data = [] + with open(jp_webhooks_file, 'w') as outfile: + json.dump(data, outfile, indent=4) + + if 'username' in session: + + # response = requests.get(session['url'] + '/JSSResource/computergroups', + # auth=(session['username'], session['password']), + # headers={'Accept': 'application/json'}) + + # response_json = response.json() + + # computer_groups = response_json['computer_groups'] + # found_computer_groups = [] + # for computer_group in computer_groups: + # if computer_group['is_smart'] is True: + # found_computer_groups.append(computer_group) + + # print found_computer_groups + + # response = requests.get(session['url'] + '/JSSResource/mobiledevicegroups', + # auth=(session['username'], session['password']), + # headers={'Accept': 'application/json'}) + + # response_json = response.json() + + # mobile_device_groups = response_json['mobile_device_groups'] + # found_mobile_device_groups = [] + # for mobile_device_group in mobile_device_groups: + # if mobile_device_group['is_smart'] is True: + # found_mobile_device_groups.append(mobile_device_group) + + # print found_mobile_device_groups + + if request.method == 'POST': + if request.form.get('webhookname') != '': + check = 0 + if ' ' in request.form.get('webhookname'): + error_message = "Single-string name only." + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + with open(webhook_conf) as json_file: + data = json.load(json_file) + + x = 0 + id_list = [] + while True: + try: + id_list.append(data[x]['id']) + x += 1 + str_error = None + except Exception as str_error: + pass + if str_error: + sleep(2) + break + else: + continue + + for id_name in id_list: + if id_name == request.form.get('webhookname'): + check = 1 + else: + check = 0 + + if check is not 0: + error_message = "Name already exists!" + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + with open(server_json_file) as json_file: + data = json.load(json_file) + server_address = data[0]['jawa_address'] + + # if not os.path.isdir('/usr/local/jawa/'): + # os.mkdir('/usr/local/jawa/') + if not os.path.isdir(scripts_dir): + os.mkdir(scripts_dir) + + os.chdir(scripts_dir) + + f = request.files['script'] + if ' ' in f.filename: + f.filename = f.filename.replace(" ", "-") + + f.save(secure_filename(f.filename)) + + old_script_file = os.path.abspath( + os.path.join(scripts_dir, f"{f.filename}")) + new_script_file = (os.path.join(scripts_dir, + f"{request.form.get('webhookname')}-{f.filename}")) + os.rename(old_script_file, new_script_file) + hooks_file = (os.path.join(os.path.join(os.path.dirname(__file__), "..", "data"), "webhook.conf")) + + if not os.path.exists(hooks_file): + with open(hooks_file, "w") as fout: + fout.write("[]") + data = json.load(open(hooks_file)) + + new_id = request.form.get('new_webhookname') + + os.chmod(new_script_file, mode=0o0755) + + if type(data) is dict: + data = [data] + + data.append({"id": request.form.get('webhookname'), + "execute-command": new_script_file, + "command-working-directory": "/", + "pass-arguments-to-command": [{"source": "entire-payload"}]}) + + # with open(hooks_file, 'w') as outfile: + # json.dump(data, outfile) + + # hooks_file = '/etc/webhook.conf' + data = json.load(open(hooks_file)) + + data[:] = [d for d in data if d.get('id') != 'none'] + + with open(hooks_file, 'w') as outfile: + json.dump(data, outfile, indent=4) + + if ( + request.form.get('event') == 'SmartGroupMobileDeviceMembershipChange' or + request.form.get('event') == 'SmartGroupComputerMembershipChange'): + + smart_group_notice = "NOTICE! This webhook is not yet enabled." + smart_group_instructions = "Specify desired Smart Group and enable: " + webhook_enablement = 'false' + + else: + smart_group_notice = "" + smart_group_instructions = "" + webhook_enablement = 'true' + data = '' + data += '' + data += request.form.get('webhookname') + data += '' + webhook_enablement + '' + data += "{}/hooks/{}".format(server_address, request.form.get('webhookname')) + data += 'application/json' + data += '{}'.format(request.form.get('event')) + data += '' + full_url = session['url'] + '/JSSResource/webhooks/id/0' + + response = requests.post(full_url, + auth=(session['username'], session['password']), + headers={'Content-Type': 'application/xml'}, data=data, + verify=verify_ssl) + + result = re.search('(.*)', response.text) + new_link = "{}/webhooks.html?id={}".format(session['url'], result.group(1)) + + data = json.load(open(jp_webhooks_file)) + + data.append({"url": str(session['url']), + "username": str(session['username']), + "name": request.form.get('webhookname'), + "event": request.form.get('event'), + "script": new_script_file, + "description": request.form.get('description')}) + + with open(jp_webhooks_file, 'w') as outfile: + json.dump(data, outfile, indent=4) + + new_here = "Link" + new_webhook = "New webhook created." + + return render_template('success.html', + webhooks="success", + smart_group_instructions=smart_group_instructions, + smart_group_notice=smart_group_notice, + new_link=new_link, + new_here=new_here, + new_webhook=new_webhook, + username=str(escape(session['username']))) + + + else: + return render_template('webhooks.html', + webhooks="webhooks", + url=session['url'], + # found_mobile_device_groups=found_mobile_device_groups, + # found_computer_groups=found_computer_groups, + username=str(escape(session['username']))) + + else: + return render_template('home.html', login="false") From acd1bb7465168a9d19cc622837b60255e7252733 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 29 Apr 2021 00:47:48 -0500 Subject: [PATCH 009/168] + switched webhook handler to jawa_receiver.py + now runs on macOS --- webapp.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/webapp.py b/webapp.py index cc7af16..72c058d 100644 --- a/webapp.py +++ b/webapp.py @@ -1,5 +1,6 @@ #!/usr/bin/python3 # encoding: utf-8 +import io import os import json import glob @@ -36,16 +37,19 @@ def main(): def environment_setup(project_dir): global webhook_file, jp_file, okta_file, cron_file, server_json_file, scripts_directory - webhook_file = "/etc/webhook.conf" - jp_file = os.path.join(project_dir, 'data/jp_webhooks.json') - okta_file = os.path.join(project_dir, 'data/okta_json.json') - cron_file = os.path.join(project_dir, 'data/cron.json') - server_json_file = os.path.join(os.path.join(project_dir, 'webapp'), 'data/jp_webhooks.json') + webhook_file = os.path.join(project_dir, 'data', 'webhook.conf') + jp_file = os.path.join(project_dir, 'data', 'jp_webhooks.json') + okta_file = os.path.join(project_dir, 'data', 'okta_json.json') + cron_file = os.path.join(project_dir, 'data', 'cron.json') + server_json_file = os.path.join(project_dir, 'data', 'server.json') scripts_directory = os.path.join(project_dir, 'scripts') # return webhook_file, jp_file, okta_file, cron_file, server_json_file, scripts_directory def register_blueprints(): + # JAWA Receiver + from webhook import jawa_receiver + app.register_blueprint(jawa_receiver.blueprint) # New Jamf Pro Webhook from webapp.new_jp_webhook import new_jp app.register_blueprint(new_jp) @@ -93,8 +97,11 @@ def setup(): server_json = [{'jawa_address': server_url, 'jps_url': jps_url}] json.dump(server_json, outfile) if os.path.isfile(server_json_file): - with open(server_json_file) as outfile: - data = json.load(outfile) + with open(server_json_file, "w") as outfile: + server_json = [{'jawa_address': server_url, 'jps_url': jps_url}] + json.dump(server_json, outfile) + with open(server_json_file, "r") as fin: + data = json.load(fin) data[0].update(new_json) with open(server_json_file, "w+") as outfile: json.dump(data, outfile) @@ -239,12 +246,12 @@ def home(): @app.route("/wizard") def wizard(): - if not os.path.isfile(webhook_file): - return redirect(url_for('setup')) + # if not os.path.isfile(webhook_file): + # return redirect(url_for('setup')) with open(jp_file) as webhook_json: webhooks_installed = json.load(webhook_json) - webhook_json = [] + webhook_json = webhooks_installed response = requests.get( session['url'] + '/JSSResource/webhooks', @@ -323,11 +330,11 @@ def wizard(): webhook_url = session['url'] - if webhook_json == []: + if not webhook_json: webhook_json = '' - if crons_json == []: + if not crons_json: crons_json = '' - if oktas_json == []: + if not oktas_json: oktas_json = '' if webhook_json == crons_json == oktas_json: @@ -345,8 +352,12 @@ def wizard(): @app.route("/first_automation") def first_automation(): - if not os.path.isfile(webhook_file): - return redirect(url_for('setup')) + # if not os.path.isfile(webhook_file): + # return redirect(url_for('setup')) + # with open(webhook_file, "r") as fin: + # webhook_json = json.load(fin) + # if not webhook_json: + # return redirect(url_for('setup')) if 'username' in session: return render_template( From ddbcb552011178ef7ad1752f5daf84fd85335825 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 12 May 2021 23:42:49 -0500 Subject: [PATCH 010/168] Resolved #15 - relative paths for cron. + linting, and misc cleanup --- webapp/delete_cron_job.py | 123 ++++++++--------- webapp/new_cron_job.py | 279 +++++++++++++++++++------------------- 2 files changed, 203 insertions(+), 199 deletions(-) diff --git a/webapp/delete_cron_job.py b/webapp/delete_cron_job.py index d4d76a0..84180f7 100644 --- a/webapp/delete_cron_job.py +++ b/webapp/delete_cron_job.py @@ -1,69 +1,70 @@ #!/usr/bin/python # encoding: utf-8 -import os import json -from time import sleep -import re +import os + from crontab import CronTab -from werkzeug.utils import secure_filename -from flask import (Flask, request, render_template, - session, redirect, url_for, escape, - send_from_directory, Blueprint, abort) +from flask import (request, render_template, + session, escape, + Blueprint) + +cron_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'cron.json')) +scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) cron_delete = Blueprint('delete_cron', __name__) -@cron_delete.route('/delete_cron', methods=['GET','POST']) + +@cron_delete.route('/delete_cron', methods=['GET', 'POST']) def delete_cron(): - exists = os.path.isfile('/usr/local/jawa/cron.json') - if exists == False: - return render_template('cron.html', - cron="cron", - username=str(escape(session['username']))) - - if 'username' in session: - text = open('/usr/local/jawa/cron.json', 'r+') - content = text.read() - webhook_data = json.loads(content) - i = 0 - names = [] - for item in webhook_data: - names.append(str(webhook_data[i]['name'])) - i += 1 - - content = names - - if request.method == 'POST': - - timed_job = request.form.get('timed_job') - - cron_file = '/usr/local/jawa/cron.json' - data = json.load(open(cron_file)) - - for d in data : - if d['name'] == timed_job: - scriptPath=(d['script']) - newScriptPath = scriptPath + '.old' - os.rename(scriptPath, newScriptPath) - - cron = CronTab(user='root') - - for job in cron: - if job.comment == timed_job: - cron.remove(job) - - data[:] = [d for d in data if d.get('name') != timed_job ] - - with open(cron_file, 'w') as outfile: - json.dump(data, outfile) - - return render_template('success.html', - webhooks="success", - username=str(escape(session['username']))) - else: - return render_template('delete_cron.html', - content=content, - delete_cron="delete_cron", - username=str(escape(session['username']))) - else: - return render_template('home.html', - login="false") \ No newline at end of file + exists = os.path.isfile(cron_json_file) + if not exists: + return render_template('cron.html', + cron="cron", + username=str(escape(session['username']))) + + if 'username' in session: + text = open(cron_json_file, 'r+') + content = text.read() + webhook_data = json.loads(content) + i = 0 + names = [] + for item in webhook_data: + names.append(str(webhook_data[i]['name'])) + i += 1 + + content = names + + if request.method == 'POST': + + timed_job = request.form.get('timed_job') + + data = json.load(open(cron_json_file)) + + for d in data: + if d['name'] == timed_job: + script_path = (d['script']) + new_script_path = script_path + '.old' + os.rename(script_path, new_script_path) + + cron = CronTab(user='root') + + for job in cron: + if job.comment == timed_job: + cron.remove(job) + + data[:] = [d for d in data if d.get('name') != timed_job] + + with open(cron_json_file, 'w') as outfile: + json.dump(data, outfile) + + return render_template('success.html', + webhooks="success", + username=str(escape(session['username']))) + else: + return render_template('delete_cron.html', + content=content, + delete_cron="delete_cron", + username=str(escape(session['username']))) + else: + return render_template('home.html', + login="false") diff --git a/webapp/new_cron_job.py b/webapp/new_cron_job.py index 482c8ae..961a1ce 100644 --- a/webapp/new_cron_job.py +++ b/webapp/new_cron_job.py @@ -5,147 +5,150 @@ from time import sleep import re from werkzeug.utils import secure_filename -from flask import (Flask, request, render_template, - session, redirect, url_for, escape, - send_from_directory, Blueprint, abort) +from flask import (Flask, request, render_template, + session, redirect, url_for, escape, + send_from_directory, Blueprint, abort) from crontab import CronTab +cron_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'cron.json')) +time_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'time.json')) +scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) + new_cron = Blueprint('cron', __name__) -@new_cron.route('/cron', methods=['GET','POST']) + +@new_cron.route('/cron', methods=['GET', 'POST']) def cron(): - global days - global hours - global frequencies - - text = open('/usr/local/jawa/time.json', 'r+') - content = text.read() - time_data = json.loads(content) - days_data = time_data['days'] - hours_data = time_data['hours'] - frequencies_data = time_data['frequencies'] - - i = 0 - days = [] - for item in days_data: - days.append(days_data[i]) - i += 1 - - i = 0 - hours = [] - for item in hours_data: - hours.append(hours_data[i]) - i += 1 - - i = 0 - frequencies = [] - for item in frequencies_data: - frequencies.append(frequencies_data[i]) - i += 1 - - if 'username' in session: - if request.method == 'POST': - if ' ' in request.form.get('cron_name'): - error_message = "Single-string name only." - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - cron_description = request.form.get('cron_description') - cron_name = request.form.get('cron_name') - - if not os.path.isdir('/usr/local/jawa/scripts'): - os.mkdir('/usr/local/jawa/scripts') - - os.chdir('/usr/local/jawa/scripts') - script = request.files['script'] - if ' ' in script.filename: - script.filename = script.filename.replace(" ", "-") - print(str(script.filename)) - - script.save(secure_filename(script.filename)) - script_file = "/usr/local/jawa/scripts/{}".format(script.filename) - - os.chmod(script_file, mode=0o0755) - - frequency = request.form.get('frequency') - - cron_json = '/usr/local/jawa/cron.json' - - if not os.path.isfile('/usr/local/jawa/cron.json'): - data = [] - with open(cron_json, 'w') as outfile: - json.dump(data, outfile) - - data = json.load(open(cron_json)) - - for i in data: - print(i) - print(" ~ ~ ~ ~ ~") - if str(i['name']) == cron_name: - error_message = "Name already exists!" - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - data.append({"name": cron_name, - "description": cron_description, - "frequency": frequency, - "script": script_file}) - - with open(cron_json, 'w') as outfile: - json.dump(data, outfile) - - cron = CronTab(user='root') - - if frequency == "everyhour": - job1 = cron.new(command=script_file, comment=cron_name) - job1.every().hours() - job1.minute.on(0) - cron.write() - - - if frequency == "everyday": - time = request.form.get('daytime') - job1 = cron.new(command=script_file, comment=cron_name) - job1.day.every(1) - job1.hour.on(time) - job1.minute.on(0) - cron.write() - - if frequency == "everyweek": - day = request.form.get('weekday') - time = request.form.get('weektime') - job1 = cron.new(command=script_file, comment=cron_name) - job1.dow.on(day) - job1.hour.on(time) - job1.minute.on(0) - cron.write() - - if frequency == "everymonth": - day = request.form.get('monthday') - time = request.form.get('monthtime') - job1 = cron.new(command=script_file, comment=cron_name) - job1.day.on(day) - job1.hour.on(time) - job1.minute.on(0) - cron.write() - - return render_template('success.html', - webhooks="success", - username=str(escape(session['username']))) - - else: - return render_template('cron.html', - cron="cron", - frequencies=frequencies, - days=days, - hours=hours, - username=str(escape(session['username']))) - - else: - return render_template('home.html', - login="false") + global days + global hours + global frequencies + + with open(time_json_file, 'r+') as fin: + content = fin.read() + time_data = json.loads(content) + + days_data = time_data['days'] + hours_data = time_data['hours'] + frequencies_data = time_data['frequencies'] + + i = 0 + days = [] + for item in days_data: + days.append(days_data[i]) + i += 1 + + i = 0 + hours = [] + for item in hours_data: + hours.append(hours_data[i]) + i += 1 + + i = 0 + frequencies = [] + for item in frequencies_data: + frequencies.append(frequencies_data[i]) + i += 1 + + if 'username' in session: + if request.method == 'POST': + if ' ' in request.form.get('cron_name'): + error_message = "Single-string name only." + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + cron_description = request.form.get('cron_description') + cron_name = request.form.get('cron_name') + + if not os.path.isdir(scripts_dir): + os.mkdir(scripts_dir) + + os.chdir(scripts_dir) + script = request.files['script'] + if ' ' in script.filename: + script.filename = script.filename.replace(" ", "-") + print(str(script.filename)) + + script.save(secure_filename(script.filename)) + script_file = os.path.join(scripts_dir, script.filename) + + os.chmod(script_file, mode=0o0755) + + frequency = request.form.get('frequency') + + if not os.path.isfile(cron_json_file): + data = [] + with open(cron_json_file, 'w') as outfile: + json.dump(data, outfile) + + data = json.load(open(cron_json_file)) + + for i in data: + print(i) + print(" ~ ~ ~ ~ ~") # Mister Krabs + if str(i['name']) == cron_name: + error_message = "Name already exists!" + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + data.append({"name": cron_name, + "description": cron_description, + "frequency": frequency, + "script": script_file}) + + with open(cron_json_file, 'w') as outfile: + json.dump(data, outfile) + + cron = CronTab(user='root') + + if frequency == "everyhour": + job1 = cron.new(command=script_file, comment=cron_name) + job1.every().hours() + job1.minute.on(0) + cron.write() + + if frequency == "everyday": + time = request.form.get('daytime') + job1 = cron.new(command=script_file, comment=cron_name) + job1.day.every(1) + job1.hour.on(time) + job1.minute.on(0) + cron.write() + + if frequency == "everyweek": + day = request.form.get('weekday') + time = request.form.get('weektime') + job1 = cron.new(command=script_file, comment=cron_name) + job1.dow.on(day) + job1.hour.on(time) + job1.minute.on(0) + cron.write() + + if frequency == "everymonth": + day = request.form.get('monthday') + time = request.form.get('monthtime') + job1 = cron.new(command=script_file, comment=cron_name) + job1.day.on(day) + job1.hour.on(time) + job1.minute.on(0) + cron.write() + + return render_template('success.html', + webhooks="success", + username=str(escape(session['username']))) + + else: + return render_template('cron.html', + cron="cron", + frequencies=frequencies, + days=days, + hours=hours, + username=str(escape(session['username']))) + + else: + return render_template('home.html', + login="false") From 3525f53d1f13ad325539b32ea285a79f360d1153 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 12 May 2021 23:43:16 -0500 Subject: [PATCH 011/168] Resolved #15 - relative paths for cron. + linting, and misc cleanup --- data/server.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/server.json b/data/server.json index 0637a08..0e6f2cb 100644 --- a/data/server.json +++ b/data/server.json @@ -1 +1 @@ -[] \ No newline at end of file +[{"jawa_address": "http://turnip.ball.farm:4242", "jps_url": "https://shinra.jamfcloud.com"}] \ No newline at end of file From 12e6bdc38863321af622109983ae2b1469d3f367 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 12 May 2021 23:44:36 -0500 Subject: [PATCH 012/168] Flexing our braces --- data/server.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/server.json b/data/server.json index 0e6f2cb..9e26dfe 100644 --- a/data/server.json +++ b/data/server.json @@ -1 +1 @@ -[{"jawa_address": "http://turnip.ball.farm:4242", "jps_url": "https://shinra.jamfcloud.com"}] \ No newline at end of file +{} \ No newline at end of file From dba84ab2f4f7966205702d356d40bf7dca94eb3d Mon Sep 17 00:00:00 2001 From: ball42 <47579551+ball42@users.noreply.github.com> Date: Wed, 12 May 2021 23:50:47 -0500 Subject: [PATCH 013/168] Misc cleanup --- data/server.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 data/server.json diff --git a/data/server.json b/data/server.json deleted file mode 100644 index 9e26dfe..0000000 --- a/data/server.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file From f2921340dc4cfc1f0b5dacde846f6db49a85f6a7 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 12 May 2021 23:58:29 -0500 Subject: [PATCH 014/168] Removing unnecessary file stub --- data/server.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 data/server.json diff --git a/data/server.json b/data/server.json deleted file mode 100644 index 9e26dfe..0000000 --- a/data/server.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file From 111c22f8aa80bc3ca22df857d6ebebf49233f6fa Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 13 May 2021 00:03:26 -0500 Subject: [PATCH 015/168] Changing base url_scheme to https --- webapp.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/webapp.py b/webapp.py index 72c058d..3fc44da 100644 --- a/webapp.py +++ b/webapp.py @@ -23,6 +23,7 @@ # global distilled_serial app = Flask(__name__) + # Initiate Flask @@ -32,7 +33,7 @@ def main(): environment_setup(base_dir) register_blueprints() app.secret_key = "*" - serve(app, url_scheme='http', host='0.0.0.0', port=8000) + serve(app, url_scheme='https', host='0.0.0.0', port=8000) def environment_setup(project_dir): @@ -155,8 +156,8 @@ def login(): with open(server_json_file) as json_file: server_json = json.load(json_file) - if 'jps_url' in server_json[0]: - if server_json[0]['jps_url'] != None and len(server_json[0]['jps_url']) != 0: + if server_json.get('jps_url', 0): + if server_json['jps_url'] != None and len(server_json[0]['jps_url']) != 0: session['url'] = str(server_json[0]['jps_url']) else: session['url'] = request.form['url'] @@ -199,10 +200,15 @@ def login(): def index(): if not os.path.isfile(server_json_file): return render_template('home.html') + with open(server_json_file, "r") as fin: + server_json = json.load(fin) + if not server_json: + return render_template('home.html') else: with open(server_json_file) as json_file: server_json = json.load(json_file) print(server_json) + if not 'jps_url' in server_json[0]: return render_template('home.html') elif server_json[0]['jps_url'] == None: @@ -230,7 +236,7 @@ def home(): print(server_json) if not 'jps_url' in server_json[0]: return render_template('home.html') - elif server_json[0]['jps_url'] == None: + elif server_json[0]['jps_url'] is None: return render_template('home.html') elif len(server_json[0]['jps_url']) == 0: return render_template('home.html') @@ -307,6 +313,7 @@ def wizard(): crons_installed = json.load(cron_json) crons_json = [] for cron in crons_installed: + script = cron['script'].rsplit('/', 1) crons_json.append({"name": cron['name'], "frequency": cron['frequency'], From 88e1333b8d7365dc780bb8f9a48ff9dd16596b0f Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 2 Jun 2021 21:30:16 -0500 Subject: [PATCH 016/168] Removing webhook.conf references from delete_jp_webhook.py, linting home.html --- data/jp_webhooks.json | 13 +++++- requirements.txt | 2 +- static/css/main.css | 79 +++++++++++++++++++++++++++++++++++++ templates/home.html | 31 ++++++++------- templates/webhooks.html | 26 ++---------- templates/wizardlayout.html | 4 ++ webapp.py | 40 +++++++++---------- webapp/delete_jp_webhook.py | 1 - webapp/new_jp_webhook.py | 42 +++++++++++++++----- 9 files changed, 167 insertions(+), 71 deletions(-) diff --git a/data/jp_webhooks.json b/data/jp_webhooks.json index 0637a08..fe844cf 100644 --- a/data/jp_webhooks.json +++ b/data/jp_webhooks.json @@ -1 +1,12 @@ -[] \ No newline at end of file +[ + { + "url": "https://shinra.jamfcloud.com", + "jawa_admin": "hojo", + "name": "my_brand_new_webhook", + "webhook_username": "itsnot", + "webhook_password": "wednesday", + "event": "RestAPIOperation", + "script": "/Users/ball/PycharmProjects/JAWA/scripts/my_brand_new_webhook-print.py", + "description": "check it out" + } +] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 79cefef..70ced61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ requests~=2.25.1 -crontab~=0.22.9 +python-crontab~=2.5.1 Werkzeug~=1.0.1 Flask~=1.1.2 waitress~=2.0.0 \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css index acd7a8c..a4b53dd 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -191,4 +191,83 @@ h3 { @-webkit-keyframes wk-rotateAnimation { 0% {-webkit-transform: rotate(0deg);} 100% {-webkit-transform: rotate(360deg);} +} + +.form-container { + padding: 25px; +} + +form.account-form > * { + margin-top: 5px; + margin-bottom: 5px; +} + +form.account-form h1 { + text-align: center; +} + +form.account-form button { + float: right; +} + +form.account-form select { + padding: 0; +} + +form.account-form { + margin-top: auto; + margin-left: auto; + margin-right: auto; + max-width: 450px; + background-color: #c9c9c9; + border: 1px solid gray; + border-radius: 5px; + padding: 10px; +} + +.error-msg { + color: #b30000; + text-align: center; +} + +.diagnosis { + padding: 400px; + color: #2b6ca8; + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 40px; + margin-right: 40px; + background-color: pink; +} + +.success-wrap { + + overflow: hidden; + border-left:1em solid transparent; + border-right:1em solid transparent; + text-overflow: ellipsis; +} + +.success-wrap:hover { + overflow: visible; +} + +.success-form { + margin-top: auto; + margin-left: auto; + margin-right: auto; + max-width: 1000px; + background-color: #c9c9c9; + border: 1px solid gray; + border-radius: 5px; + padding: 10px; +} + +.button-align { + align-items: right; +} + +.test-banner { + background-color: #ececec; } \ No newline at end of file diff --git a/templates/home.html b/templates/home.html index d8da07b..5a839c7 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,28 +1,29 @@ {% macro input(name, value='', type='text', size=20) -%} - + {%- endmacro %} - {% extends "homelayout.html" %} - {% block content %} - diff --git a/templates/webhooks.html b/templates/webhooks.html index 085fc69..280146f 100644 --- a/templates/webhooks.html +++ b/templates/webhooks.html @@ -91,29 +91,9 @@

New Webhook



Description (optional):


- + Auth:
+ Username (optional): + Password (optional):
diff --git a/templates/wizardlayout.html b/templates/wizardlayout.html index b0b03b2..3dd2a70 100644 --- a/templates/wizardlayout.html +++ b/templates/wizardlayout.html @@ -34,6 +34,10 @@

diff --git a/webapp.py b/webapp.py index 3fc44da..c2ce3d6 100644 --- a/webapp.py +++ b/webapp.py @@ -33,7 +33,7 @@ def main(): environment_setup(base_dir) register_blueprints() app.secret_key = "*" - serve(app, url_scheme='https', host='0.0.0.0', port=8000) + serve(app, url_scheme='http', host='0.0.0.0', port=8000) def environment_setup(project_dir): @@ -95,7 +95,7 @@ def setup(): print(new_json) if not os.path.isfile(server_json_file): with open(server_json_file, "w") as outfile: - server_json = [{'jawa_address': server_url, 'jps_url': jps_url}] + server_json = {'jawa_address': server_url, 'jps_url': jps_url} json.dump(server_json, outfile) if os.path.isfile(server_json_file): with open(server_json_file, "w") as outfile: @@ -103,7 +103,7 @@ def setup(): json.dump(server_json, outfile) with open(server_json_file, "r") as fin: data = json.load(fin) - data[0].update(new_json) + data.update(new_json) with open(server_json_file, "w+") as outfile: json.dump(data, outfile) @@ -157,8 +157,8 @@ def login(): server_json = json.load(json_file) if server_json.get('jps_url', 0): - if server_json['jps_url'] != None and len(server_json[0]['jps_url']) != 0: - session['url'] = str(server_json[0]['jps_url']) + if server_json['jps_url'] != None and len(server_json['jps_url']) != 0: + session['url'] = str(server_json['jps_url']) else: session['url'] = request.form['url'] else: @@ -209,14 +209,14 @@ def index(): server_json = json.load(json_file) print(server_json) - if not 'jps_url' in server_json[0]: + if not 'jps_url' in server_json: return render_template('home.html') - elif server_json[0]['jps_url'] == None: + elif server_json['jps_url'] == None: return render_template('home.html') - elif len(server_json[0]['jps_url']) == 0: + elif len(server_json['jps_url']) == 0: return render_template('home.html') else: - session['url'] = server_json[0]['jps_url'] + session['url'] = server_json['jps_url'] return render_template('home.html', jps_url=str(escape(session['url'])), welcome="true", jsslock="true") @@ -234,14 +234,14 @@ def home(): with open(server_json_file) as json_file: server_json = json.load(json_file) print(server_json) - if not 'jps_url' in server_json[0]: + if not 'jps_url' in server_json: return render_template('home.html') - elif server_json[0]['jps_url'] is None: + elif server_json['jps_url'] is None: return render_template('home.html') - elif len(server_json[0]['jps_url']) == 0: + elif len(server_json['jps_url']) == 0: return render_template('home.html') else: - session['url'] = server_json[0]['jps_url'] + session['url'] = server_json['jps_url'] return render_template('home.html', jps_url=str(escape(session['url'])), welcome="true", jsslock="true") @@ -295,13 +295,13 @@ def wizard(): jamf_event = response_json['webhook']['event'] jamf_id = response_json['webhook']['id'] - - script = webhook['script'].rsplit('/', 1) - webhook_json.append({"name": webhook['name'], - "jamf_id": jamf_id, - "event": jamf_event, - "script": script[1], - "description": webhook['description']}) + print(webhook['script']) + script = webhook['script'] + # webhook_json.append({"name": webhook['name'], + # "jamf_id": jamf_id, + # "event": jamf_event, + # "script": script, + # "description": webhook['description']}) data = [] diff --git a/webapp/delete_jp_webhook.py b/webapp/delete_jp_webhook.py index 04ec4e7..f4d25b6 100644 --- a/webapp/delete_jp_webhook.py +++ b/webapp/delete_jp_webhook.py @@ -17,7 +17,6 @@ server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) -webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) delete_jp = Blueprint('delete', __name__) diff --git a/webapp/new_jp_webhook.py b/webapp/new_jp_webhook.py index 66b537d..3b42c1a 100644 --- a/webapp/new_jp_webhook.py +++ b/webapp/new_jp_webhook.py @@ -112,7 +112,7 @@ def webhooks(): with open(server_json_file) as json_file: data = json.load(json_file) - server_address = data[0]['jawa_address'] + server_address = data['jawa_address'] # if not os.path.isdir('/usr/local/jawa/'): # os.mkdir('/usr/local/jawa/') @@ -174,14 +174,33 @@ def webhooks(): smart_group_notice = "" smart_group_instructions = "" webhook_enablement = 'true' - data = '' - data += '' - data += request.form.get('webhookname') - data += '' + webhook_enablement + '' - data += "{}/hooks/{}".format(server_address, request.form.get('webhookname')) - data += 'application/json' - data += '{}'.format(request.form.get('event')) - data += '' + + # Check for auth values + auth_xml = "NONE" + if ( + request.form.get('username') != '' or + request.form.get('password') != '' ): + auth_xml = f"BASIC" + if request.form.get('username') == '': + auth_xml += "null" + else: + auth_xml += f"{request.form.get('username')}" + if request.form.get('password') == '': + auth_xml += "null" + else: + auth_xml += f"{request.form.get('password')}" + + + + data = f"" \ + f"{request.form.get('webhookname')}" \ + f"{webhook_enablement}" \ + f"{server_json['jawa_address']}/hooks/{request.form.get('webhookname')}" \ + f"application/json" \ + f"{request.form.get('event')}" \ + f"{auth_xml}" \ + f"" + full_url = session['url'] + '/JSSResource/webhooks/id/0' response = requests.post(full_url, @@ -189,14 +208,17 @@ def webhooks(): headers={'Content-Type': 'application/xml'}, data=data, verify=verify_ssl) + result = re.search('(.*)', response.text) new_link = "{}/webhooks.html?id={}".format(session['url'], result.group(1)) data = json.load(open(jp_webhooks_file)) data.append({"url": str(session['url']), - "username": str(session['username']), + "jawa_admin": str(session['username']), "name": request.form.get('webhookname'), + "webhook_username": request.form.get('username'), + "webhook_password": request.form.get('password'), "event": request.form.get('event'), "script": new_script_file, "description": request.form.get('description')}) From 1afb1dc88572cc9b46f84da006c6c8462b8120be Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 2 Jun 2021 21:36:17 -0500 Subject: [PATCH 017/168] Setting waitress url scheme to https, yet again... --- webapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp.py b/webapp.py index c2ce3d6..dd3ac74 100644 --- a/webapp.py +++ b/webapp.py @@ -33,7 +33,7 @@ def main(): environment_setup(base_dir) register_blueprints() app.secret_key = "*" - serve(app, url_scheme='http', host='0.0.0.0', port=8000) + serve(app, url_scheme='https', host='0.0.0.0', port=8000) def environment_setup(project_dir): From 7ccfe46c291f7b1bb0bcb0524ed2ca17bec2b757 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 2 Jun 2021 21:40:40 -0500 Subject: [PATCH 018/168] Starting empty list --- data/jp_webhooks.json | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/data/jp_webhooks.json b/data/jp_webhooks.json index fe844cf..0637a08 100644 --- a/data/jp_webhooks.json +++ b/data/jp_webhooks.json @@ -1,12 +1 @@ -[ - { - "url": "https://shinra.jamfcloud.com", - "jawa_admin": "hojo", - "name": "my_brand_new_webhook", - "webhook_username": "itsnot", - "webhook_password": "wednesday", - "event": "RestAPIOperation", - "script": "/Users/ball/PycharmProjects/JAWA/scripts/my_brand_new_webhook-print.py", - "description": "check it out" - } -] \ No newline at end of file +[] \ No newline at end of file From ea49a80c568d238995fabda8d09d41604f944cb2 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 3 Jun 2021 00:42:54 -0500 Subject: [PATCH 019/168] Added basic auth options for Jamf Pro webhooks. Completed the change from webhook.conf to jp_webhooks.json. --- templates/edit.html | 15 ++------ webapp.py | 17 ++------- webapp/edit_jp_webhook.py | 45 +++++++++++------------ webapp/new_jp_webhook.py | 77 ++++++++++----------------------------- webhook/jawa_receiver.py | 11 ++++-- 5 files changed, 55 insertions(+), 110 deletions(-) diff --git a/templates/edit.html b/templates/edit.html index a0d4a62..5c50401 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -70,18 +70,9 @@

Edit Webhook



- + Auth:
+ Username (optional): + Password (optional):
diff --git a/webapp.py b/webapp.py index dd3ac74..7fe6bd9 100644 --- a/webapp.py +++ b/webapp.py @@ -37,8 +37,8 @@ def main(): def environment_setup(project_dir): - global webhook_file, jp_file, okta_file, cron_file, server_json_file, scripts_directory - webhook_file = os.path.join(project_dir, 'data', 'webhook.conf') + global jp_file, okta_file, cron_file, server_json_file, scripts_directory + # webhook_file = os.path.join(project_dir, 'data', 'webhook.conf') jp_file = os.path.join(project_dir, 'data', 'jp_webhooks.json') okta_file = os.path.join(project_dir, 'data', 'okta_json.json') cron_file = os.path.join(project_dir, 'data', 'cron.json') @@ -97,7 +97,7 @@ def setup(): with open(server_json_file, "w") as outfile: server_json = {'jawa_address': server_url, 'jps_url': jps_url} json.dump(server_json, outfile) - if os.path.isfile(server_json_file): + elif os.path.isfile(server_json_file): with open(server_json_file, "w") as outfile: server_json = [{'jawa_address': server_url, 'jps_url': jps_url}] json.dump(server_json, outfile) @@ -252,9 +252,6 @@ def home(): @app.route("/wizard") def wizard(): - # if not os.path.isfile(webhook_file): - # return redirect(url_for('setup')) - with open(jp_file) as webhook_json: webhooks_installed = json.load(webhook_json) webhook_json = webhooks_installed @@ -313,7 +310,6 @@ def wizard(): crons_installed = json.load(cron_json) crons_json = [] for cron in crons_installed: - script = cron['script'].rsplit('/', 1) crons_json.append({"name": cron['name'], "frequency": cron['frequency'], @@ -359,13 +355,6 @@ def wizard(): @app.route("/first_automation") def first_automation(): - # if not os.path.isfile(webhook_file): - # return redirect(url_for('setup')) - # with open(webhook_file, "r") as fin: - # webhook_json = json.load(fin) - # if not webhook_json: - # return redirect(url_for('setup')) - if 'username' in session: return render_template( 'first_automation.html', diff --git a/webapp/edit_jp_webhook.py b/webapp/edit_jp_webhook.py index 2e15079..e37c04c 100644 --- a/webapp/edit_jp_webhook.py +++ b/webapp/edit_jp_webhook.py @@ -12,7 +12,6 @@ server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) -webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) edit_jp = Blueprint('edit', __name__) @@ -58,7 +57,7 @@ def edit(): check = 0 - with open(webhook_conf) as json_file: + with open(jp_webhooks_file) as json_file: data = json.load(json_file) x = 0 @@ -66,7 +65,7 @@ def edit(): while True: try: - id_list.append(data[x]['id']) + id_list.append(data[x]['name']) x += 1 str_error = None except Exception as str_error: @@ -92,7 +91,7 @@ def edit(): with open(server_json_file) as json_file: data = json.load(json_file) - server_address = data[0]['jawa_address'] + server_address = data['jawa_address'] if not os.path.isdir(scripts_dir): os.mkdir(scripts_dir) @@ -111,34 +110,20 @@ def edit(): os.rename(old_script_file, new_script_file) script_file = new_script_file - # webhook_conf = webhook_conf os.chmod(script_file, mode=0o0755) - with open(webhook_conf) as json_file: - data = json.load(json_file) - - for x in data: - if request.form.get('webhookname') in x['id']: - x['execute-command'] = str(script_file) - if request.form.get('new_webhookname') != '': - x['id'] = str(request.form.get('new_webhookname')) - x['event'] = str(request.form.get('event')) - # if request.form.get('event') != '': - # pass - - with open(webhook_conf, 'w') as outfile: - json.dump(data, outfile, indent=4) - with open(jp_webhooks_file) as json_file: data = json.load(json_file) for x in data: - if request.form.get('webhookname') in x['name']: + if request.form.get('webhookname') == x['name']: x['script'] = str(script_file) - x['event'] = str(request.form.get('event')) if request.form.get('new_webhookname') != '': x['name'] = str(request.form.get('new_webhookname')) + x['event'] = str(request.form.get('event')) + x['webhook_username'] = str(request.form.get('username')) + x['webhook_password'] = str(request.form.get('password')) with open(jp_webhooks_file, 'w') as outfile: json.dump(data, outfile, indent=4) @@ -148,7 +133,7 @@ def edit(): new_webhookname = webhookname with open(server_json_file) as json_file: data = json.load(json_file) - server_address = data[0]['jawa_address'] + server_address = data['jawa_address'] add_name = '' if request.form.get('new_webhookname') != "": @@ -179,6 +164,19 @@ def edit(): smart_group_notice = "" smart_group_instructions = "" webhook_enablement = 'true' + auth_xml = "NONE" + if ( + request.form.get('username') != '' or + request.form.get('password') != ''): + auth_xml = f"BASIC" + if request.form.get('username') == '': + auth_xml += "null" + else: + auth_xml += f"{request.form.get('username')}" + if request.form.get('password') == '': + auth_xml += "null" + else: + auth_xml += f"{request.form.get('password')}" if add_name == '' and add_event == '': print("No Jamf Change Needed") @@ -192,6 +190,7 @@ def edit(): data += add_name data += '' + webhook_enablement + '' data += add_event + data += auth_xml data += '' full_url = session['url'] + '/JSSResource/webhooks/name/' + webhookname response = requests.put(full_url, diff --git a/webapp/new_jp_webhook.py b/webapp/new_jp_webhook.py index 3b42c1a..0094425 100644 --- a/webapp/new_jp_webhook.py +++ b/webapp/new_jp_webhook.py @@ -11,11 +11,10 @@ session, redirect, url_for, escape, send_from_directory, Blueprint, abort) -webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) + server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) -print(server_json_file, jp_webhooks_file) verify_ssl = True new_jp = Blueprint('webhooks', __name__) @@ -40,35 +39,6 @@ def webhooks(): json.dump(data, outfile, indent=4) if 'username' in session: - - # response = requests.get(session['url'] + '/JSSResource/computergroups', - # auth=(session['username'], session['password']), - # headers={'Accept': 'application/json'}) - - # response_json = response.json() - - # computer_groups = response_json['computer_groups'] - # found_computer_groups = [] - # for computer_group in computer_groups: - # if computer_group['is_smart'] is True: - # found_computer_groups.append(computer_group) - - # print found_computer_groups - - # response = requests.get(session['url'] + '/JSSResource/mobiledevicegroups', - # auth=(session['username'], session['password']), - # headers={'Accept': 'application/json'}) - - # response_json = response.json() - - # mobile_device_groups = response_json['mobile_device_groups'] - # found_mobile_device_groups = [] - # for mobile_device_group in mobile_device_groups: - # if mobile_device_group['is_smart'] is True: - # found_mobile_device_groups.append(mobile_device_group) - - # print found_mobile_device_groups - if request.method == 'POST': if request.form.get('webhookname') != '': check = 0 @@ -79,14 +49,14 @@ def webhooks(): error="error", username=str(escape(session['username']))) - with open(webhook_conf) as json_file: + with open(jp_webhooks_file) as json_file: data = json.load(json_file) x = 0 id_list = [] while True: try: - id_list.append(data[x]['id']) + id_list.append(data[x]['name']) x += 1 str_error = None except Exception as str_error: @@ -132,35 +102,23 @@ def webhooks(): new_script_file = (os.path.join(scripts_dir, f"{request.form.get('webhookname')}-{f.filename}")) os.rename(old_script_file, new_script_file) - hooks_file = (os.path.join(os.path.join(os.path.dirname(__file__), "..", "data"), "webhook.conf")) - - if not os.path.exists(hooks_file): - with open(hooks_file, "w") as fout: - fout.write("[]") - data = json.load(open(hooks_file)) + # + # if not os.path.exists(jp_webhooks_file): + # with open(jp_webhooks_file, "w") as fout: + # fout.write("[]") + # data = json.load(open(jp_webhooks_file)) new_id = request.form.get('new_webhookname') os.chmod(new_script_file, mode=0o0755) - if type(data) is dict: - data = [data] - - data.append({"id": request.form.get('webhookname'), - "execute-command": new_script_file, - "command-working-directory": "/", - "pass-arguments-to-command": [{"source": "entire-payload"}]}) - + # if type(data) is dict: + # data = [data] + # + # data[:] = [d for d in data if d.get('id') != 'none'] + # # with open(hooks_file, 'w') as outfile: - # json.dump(data, outfile) - - # hooks_file = '/etc/webhook.conf' - data = json.load(open(hooks_file)) - - data[:] = [d for d in data if d.get('id') != 'none'] - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile, indent=4) + # json.dump(data, outfile, indent=4) if ( request.form.get('event') == 'SmartGroupMobileDeviceMembershipChange' or @@ -207,7 +165,12 @@ def webhooks(): auth=(session['username'], session['password']), headers={'Content-Type': 'application/xml'}, data=data, verify=verify_ssl) - + if response.status_code == 409: + error_message = f"The webhook name \"{request.form.get('webhookname')}\" already exists in your Jamf Pro Server." + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) result = re.search('(.*)', response.text) new_link = "{}/webhooks.html?id={}".format(session['url'], result.group(1)) diff --git a/webhook/jawa_receiver.py b/webhook/jawa_receiver.py index a24c4ce..41ece16 100644 --- a/webhook/jawa_receiver.py +++ b/webhook/jawa_receiver.py @@ -7,7 +7,6 @@ import requests import subprocess - server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) @@ -17,15 +16,19 @@ logger = logging.getLogger('waitress') -def validate_webhook(webhook_data, webhook_name): +def validate_webhook(webhook_data, webhook_name, webhook_user, webhook_pass): with open(jp_webhooks_file, "r") as fin: webhooks_json = json.load(fin) truth_test = False for each_webhook in webhooks_json: if each_webhook['name'] == webhook_name: truth_test = True - return truth_test + if ( + each_webhook['webhook_username'] != webhook_user or + each_webhook['webhook_password'] != webhook_pass): + truth_test = False + return truth_test def run_script(webhook_data, webhook_name): @@ -53,7 +56,7 @@ def jamf_webhook_handler(webhook_name): print(webhook_data) print(webhook_name) - if validate_webhook(webhook_data, webhook_name): + if validate_webhook(webhook_data, webhook_name, webhook_user, webhook_pass): print(f"{webhook_name} validated!") run_script(webhook_data, webhook_name) else: From 3dbc6938d6c4d61b7d8027cd40f1bc686ac523ca Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:42:18 -0500 Subject: [PATCH 020/168] One webhooks.json to rule them all --- data/okta_json.json | 1 - data/{jp_webhooks.json => webhooks.json} | 0 2 files changed, 1 deletion(-) delete mode 100644 data/okta_json.json rename data/{jp_webhooks.json => webhooks.json} (100%) diff --git a/data/okta_json.json b/data/okta_json.json deleted file mode 100644 index 0637a08..0000000 --- a/data/okta_json.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/data/jp_webhooks.json b/data/webhooks.json similarity index 100% rename from data/jp_webhooks.json rename to data/webhooks.json From 39dba7c89a9babbf98d2655364835ff364c297e2 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:45:19 -0500 Subject: [PATCH 021/168] Migrated Okta webhooks to new webhook engine --- bin/okta_verification.py | 17 +- templates/wizard.html | 2 +- webapp/delete_okta_webhook.py | 121 ++++++------- webapp/new_okta_webhook.py | 319 +++++++++++++++------------------- 4 files changed, 221 insertions(+), 238 deletions(-) diff --git a/bin/okta_verification.py b/bin/okta_verification.py index a811661..d421968 100755 --- a/bin/okta_verification.py +++ b/bin/okta_verification.py @@ -1,10 +1,21 @@ #!/usr/bin/python +from flask import request import json import sys -challenge = sys.argv[2] -json_text = {"verification": "{}".format(challenge)} +def verify_new_webhook(challenge): + # challenge = sys.argv[2] + # verification = request.headers.get('x-okta-verification-challenge') + json_text = {f"verification": f"{challenge}"} + print(json.dumps(json_text)) + return json_text -print(json.dumps(json_text)) + +def main(): + pass + + +if __name__ == '__main__': + main() diff --git a/templates/wizard.html b/templates/wizard.html index d03ce51..1bc563e 100644 --- a/templates/wizard.html +++ b/templates/wizard.html @@ -176,7 +176,7 @@

JAWA Dashboard

{% for row in oktas_installed %} {{row.name}} - {{row.event}} + {{row.okta_event}} {{row.script}} {% endfor %} diff --git a/webapp/delete_okta_webhook.py b/webapp/delete_okta_webhook.py index 089c739..ca72c23 100644 --- a/webapp/delete_okta_webhook.py +++ b/webapp/delete_okta_webhook.py @@ -1,84 +1,87 @@ -#!/usr/bin/python -# encoding: utf-8 +#!/usr/bin/python3 + +from collections import defaultdict import os import json import time -from time import sleep import requests -import re -from werkzeug.utils import secure_filename -from flask import (Flask, request, render_template, - session, redirect, url_for, escape, - send_from_directory, Blueprint, abort) +from flask import (request, render_template, + session, redirect, url_for, escape, + Blueprint) delete_okta = Blueprint('okta_delete', __name__) -@delete_okta.route('/okta_delete', methods=['GET','POST']) -def okta_delete(): - exists = os.path.isfile('/usr/local/jawa/webapp/server.json') - if exists == False: - return render_template('setup.html', - setup="setup", - username=str(escape(session['username']))) - - if 'username' in session: - text = open('/usr/local/jawa/okta_json.json', 'r+') - content = text.read() - webhook_data = json.loads(content) - i = 0 - names = [] - for item in webhook_data: - names.append(str(webhook_data[i]['name'])) - i += 1 +server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) +webhooks_json = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) +scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) - content = names - if request.method == 'POST': - if request.form.get('webhookname') != '': +@delete_okta.route('/okta_delete', methods=['GET', 'POST']) +def okta_delete(): + exists = os.path.isfile(server_json_file) + if exists == False: + return render_template('setup.html', + setup="setup", + username=str(escape(session['username']))) + + if 'username' in session: + with open(webhooks_json, 'r+') as fin: + content = fin.read() + webhook_data = json.loads(content) + i = 0 + names = [] + for item in webhook_data: + data = defaultdict(lambda: "MISSING", item) + tag = item['tag'] + if tag == "okta": + names.append(str(webhook_data[i]['name'])) + i += 1 - webhookname = request.form.get('webhookname') - ts = time.time() + content = names - hooks_file = '/etc/webhook.conf' - data = json.load(open(hooks_file)) + if request.method == 'POST': + if request.form.get('webhookname') != '': + webhookname = request.form.get('webhookname') + ts = time.time() - for d in data : - if d['id'] == webhookname: - scriptPath=(d['execute-command']) - newScriptPath = scriptPath + '.old' - os.rename(scriptPath, newScriptPath) - data[:] = [d for d in data if d.get('id') != webhookname ] + data = json.load(open(webhooks_json)) - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) + for d in data: + if d['id'] == webhookname: + scriptPath = (d['execute-command']) + newScriptPath = scriptPath + '.old' + os.rename(scriptPath, newScriptPath) - hooks_file = '/usr/local/jawa/okta_json.json' - data = json.load(open(hooks_file)) + data[:] = [d for d in data if d.get('id') != webhookname] - for d in data : - if d['name'] == webhookname: - response = requests.post(d['okta_url'] + '/api/v1/eventHooks/{}/lifecycle/deactivate'.format(d['okta_id']), - headers={"Authorization": "SSWS {}".format(d['okta_token'])}) + with open(webhooks_json, 'w') as outfile: + json.dump(data, outfile) - response = requests.delete(d['okta_url'] + '/api/v1/eventHooks/{}'.format(d['okta_id']), - headers={"Authorization": "SSWS {}".format(d['okta_token'])}) + data = json.load(open(webhooks_json)) - data[:] = [d for d in data if d.get('name') != webhookname ] + for d in data: + if d['name'] == webhookname: + response = requests.post( + d['okta_url'] + '/api/v1/eventHooks/{}/lifecycle/deactivate'.format(d['okta_id']), + headers={"Authorization": "SSWS {}".format(d['okta_token'])}) - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) + response = requests.delete(d['okta_url'] + '/api/v1/eventHooks/{}'.format(d['okta_id']), + headers={"Authorization": "SSWS {}".format(d['okta_token'])}) + data[:] = [d for d in data if d.get('name') != webhookname] - return redirect(url_for('success')) + with open(webhooks_json, 'w') as outfile: + json.dump(data, outfile) - else: - return render_template('okta_delete.html', - text=content, delete="delete", - username=str(escape(session['username']))) - else: - return render_template('home.html', - login="false") + return redirect(url_for('success')) + else: + return render_template('okta_delete.html', + text=content, delete="delete", + username=str(escape(session['username']))) + else: + return render_template('home.html', + login="false") diff --git a/webapp/new_okta_webhook.py b/webapp/new_okta_webhook.py index b92ace6..690a720 100644 --- a/webapp/new_okta_webhook.py +++ b/webapp/new_okta_webhook.py @@ -8,182 +8,151 @@ import requests import re from werkzeug.utils import secure_filename -from flask import (Flask, request, render_template, - session, redirect, url_for, escape, - send_from_directory, Blueprint, abort) +from flask import (Flask, request, render_template, + session, redirect, url_for, escape, + send_from_directory, Blueprint, abort) new_okta = Blueprint('okta_new', __name__) -@new_okta.route('/okta_new', methods=['GET','POST']) +server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) +okta_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'okta_json.json')) +okta_verification_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'bin', 'okta_verification.py')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) +scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) + + +@new_okta.route('/okta_new', methods=['GET', 'POST']) def okta_new(): - if 'username' in session: - os.chmod("/usr/local/jawa/okta_verification.py", mode=0o0755) - okta_json = '/usr/local/jawa/okta_json.json' - - if not os.path.isfile('/usr/local/jawa/okta_json.json'): - data = [] - with open(okta_json, 'w') as outfile: - json.dump(data, outfile) - - if request.method == 'POST': - if ' ' in request.form.get('webhookname'): - error_message = "Single-string name only." - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - with open('/usr/local/jawa/webapp/server.json') as json_file: - data = json.load(json_file) - server_address = data[0]['jawa_address'] - if not os.path.isdir('/usr/local/jawa/'): - os.mkdir('/usr/local/jawa/') - if not os.path.isdir('/usr/local/jawa/scripts'): - os.mkdir('/usr/local/jawa/scripts') - - okta_server = request.form.get('okta_server') - okta_token = request.form.get('token') - okta_name = request.form.get('webhookname') - okta_event = request.form.get('event') - webhook_server_url = server_address + '/hooks/' + okta_name - - os.chdir('/usr/local/jawa/scripts') - - f = request.files['script'] - if ' ' in f.filename: - f.filename = f.filename.replace(" ", "-") - - f.save(secure_filename(f.filename)) - - old_script_file = "/usr/local/jawa/scripts/{}".format(f.filename) - - hooks_file = '/etc/webhook.conf' - data = json.load(open(hooks_file)) - - new_id = okta_name - script_file = "/usr/local/jawa/scripts/{}".format(okta_name + "_" + f.filename) - - os.rename(old_script_file, script_file) - new_file = script_file - - okta_info = json.load(open(okta_json)) - - for i in data: - if str(i['id']) == okta_name: - error_message = "Name already exists!" - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - os.chmod(new_file, mode=0o0755) - - data = { - "name" : okta_name, - "events" : { - "type" : "EVENT_TYPE", - "items" : [okta_event] - }, - "channel" : { - "type" : "HTTP", - "version" : "1.0.0", - "config" : { - "uri" : webhook_server_url, - "headers" : [{ - "key" : "X-Other-Header", - "value" : "some-other-value" - }], - "authScheme" : { - "type" : "HEADER", - "key" : "Authorization", - "value" : "${api_key}" - } - } - } - } - - data = json.dumps(data) - - # Makes hook in Okta, gets id - response = requests.post(okta_server + '/api/v1/eventHooks', - headers={ - 'Accept': 'application/json', - "Authorization": "SSWS {}".format(okta_token), - 'Content-Type': 'application/json'}, - data=data) - - response_json = response.json() - - okta_id = response_json['id'] - - okta_info.append({"name": okta_name, - "okta_id": okta_id, - "okta_event": okta_event, - "okta_url": okta_server, - "okta_token": okta_token, - "script": script_file}) - - with open(okta_json, 'w') as outfile: - json.dump(okta_info, outfile) - - - hooks_file = '/etc/webhook.conf' - data = json.load(open(hooks_file)) - if type(data) is dict: - data = [data] - - data.append({ - "id": okta_name, - "execute-command": "/usr/local/jawa/okta_verification.py", - "response-headers": [{"name": "Content-Type","value": "application/json"}], - "include-command-output-in-response": True, - "command-working-directory": "/", - "pass-arguments-to-command": [{"source": "entire-payload"}, {"source": "header", "name": "X-Okta-Verification-Challenge"}]}) - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) - - sleep(5) - - # Verify/activate - response = requests.post(okta_server + '/api/v1/eventHooks/{}/lifecycle/verify'.format(okta_id), - headers={"Authorization": "SSWS {}".format(okta_token)}) - - verification = response.json() - if 'errorCode' in verification: - error_message = "Verification failed...try again!" - return render_template('error.html', - error_message=error_message, - error="error", - username=str(escape(session['username']))) - - # Make new official hook - hooks_file = '/etc/webhook.conf' - data = json.load(open(hooks_file)) - - data[:] = [d for d in data if d['id'] != okta_name ] - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) - - if type(data) is dict: - data = [data] - - hooks_file = '/etc/webhook.conf' - data = json.load(open(hooks_file)) - - data.append({"id": okta_name, - "execute-command": new_file, - "command-working-directory": "/", - "pass-arguments-to-command":[{"source": "entire-payload"}]}) - - with open(hooks_file, 'w') as outfile: - json.dump(data, outfile) - - return render_template('success.html', login="true") - - else: - return render_template('okta_new.html', setup="setup", username=str(escape(session['username']))) - - else: - return render_template('home.html', login="false") \ No newline at end of file + if 'username' in session: + os.chmod(okta_verification_file, mode=0o0755) + # okta_json = '/usr/local/jawa/okta_json.json' + + if not os.path.isfile(jp_webhooks_file): + data = [] + with open(jp_webhooks_file, 'w') as outfile: + json.dump(data, outfile, indent=4) + + if request.method == 'POST': + if ' ' in request.form.get('webhookname'): + error_message = "Single-string name only." + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + with open(server_json_file) as json_file: + data = json.load(json_file) + server_address = data['jawa_address'] + if not os.path.isdir('/usr/local/jawa/'): + os.mkdir('/usr/local/jawa/') + + if not os.path.isdir(scripts_dir): + os.mkdir(scripts_dir) + + okta_server = request.form.get('okta_server') + okta_token = request.form.get('token') + okta_name = request.form.get('webhookname') + okta_event = request.form.get('event') + webhook_server_url = server_address + '/hooks/' + okta_name + print(webhook_server_url) + os.chdir(scripts_dir) + + f = request.files['script'] + if ' ' in f.filename: + f.filename = f.filename.replace(" ", "-") + + f.save(secure_filename(f.filename)) + + old_script_file = os.path.join(scripts_dir, f.filename) + + # hooks_file = '/etc/webhook.conf' + data = json.load(open(jp_webhooks_file)) + + new_id = okta_name + script_file = os.path.join(scripts_dir, okta_name + "_" + f.filename) + + os.rename(old_script_file, script_file) + new_file = script_file + + okta_info = json.load(open(jp_webhooks_file)) + + for i in data: + if str(i['name']) == okta_name: + error_message = "Name already exists!" + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + os.chmod(os.path.join(scripts_dir, new_file), mode=0o0755) + + data = { + "name": okta_name, + "events": { + "type": "EVENT_TYPE", + "items": [okta_event] + }, + "channel": { + "type": "HTTP", + "version": "1.0.0", + "config": { + "uri": webhook_server_url, + "headers": [{ + "key": "X-Other-Header", + "value": "some-other-value" + }], + "authScheme": { + "type": "HEADER", + "key": "Authorization", + "value": "${api_key}" + } + } + } + } + + data = json.dumps(data, indent=4) + + # Makes hook in Okta, gets id + response = requests.post(okta_server + '/api/v1/eventHooks', + headers={ + 'Accept': 'application/json', + "Authorization": "SSWS {}".format(okta_token), + 'Content-Type': 'application/json'}, + data=data) + print(response.status_code, response.text) + response_json = response.json() + print(response_json) + okta_id = response_json['id'] + + okta_info.append({"name": okta_name, + "okta_id": okta_id, + "okta_event": okta_event, + "okta_url": okta_server, + "okta_token": okta_token, + "script": script_file, + "webhook_username": "null", + "webhook_password": "null", + "tag": "okta"}) + + with open(jp_webhooks_file, 'w') as outfile: + json.dump(okta_info, outfile, indent=4) + + # Verify/activate + response = requests.post(okta_server + '/api/v1/eventHooks/{}/lifecycle/verify'.format(okta_id), + headers={"Authorization": "SSWS {}".format(okta_token)}) + + verification = response.json() + if 'errorCode' in verification: + error_message = "Verification failed...try again!" + return render_template('error.html', + error_message=error_message, + error="error", + username=str(escape(session['username']))) + + return render_template('success.html', login="true") + + else: + return render_template('okta_new.html', setup="setup", username=str(escape(session['username']))) + + else: + return render_template('home.html', login="false") From 74f757a7d47289cd5ea4b7471e9ebb161c2a5a2b Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:49:12 -0500 Subject: [PATCH 022/168] fixed issue with delete_cron_job.py writing to crontab --- webapp/delete_cron_job.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webapp/delete_cron_job.py b/webapp/delete_cron_job.py index 84180f7..43fa123 100644 --- a/webapp/delete_cron_job.py +++ b/webapp/delete_cron_job.py @@ -50,7 +50,9 @@ def delete_cron(): for job in cron: if job.comment == timed_job: + print(f"Removing Cron job {timed_job}") cron.remove(job) + cron.write() data[:] = [d for d in data if d.get('name') != timed_job] From 62a85be4d071b6d158c2907bda328aa9b6012cb2 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:50:13 -0500 Subject: [PATCH 023/168] added "tag" key for webhooks.json entries --- webapp/new_jp_webhook.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/webapp/new_jp_webhook.py b/webapp/new_jp_webhook.py index 0094425..d8fbeb2 100644 --- a/webapp/new_jp_webhook.py +++ b/webapp/new_jp_webhook.py @@ -13,7 +13,7 @@ server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) -jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) verify_ssl = True new_jp = Blueprint('webhooks', __name__) @@ -73,7 +73,7 @@ def webhooks(): else: check = 0 - if check is not 0: + if check != 0: error_message = "Name already exists!" return render_template('error.html', error_message=error_message, @@ -184,7 +184,8 @@ def webhooks(): "webhook_password": request.form.get('password'), "event": request.form.get('event'), "script": new_script_file, - "description": request.form.get('description')}) + "description": request.form.get('description'), + "tag": "jamfpro"}) with open(jp_webhooks_file, 'w') as outfile: json.dump(data, outfile, indent=4) From d601f7ab9ddcf92acc87e5728c8546fa41590a62 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:51:06 -0500 Subject: [PATCH 024/168] Smarter naming for new_cron_job.py's uploaded scripts --- webapp/new_cron_job.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/new_cron_job.py b/webapp/new_cron_job.py index 961a1ce..02534b0 100644 --- a/webapp/new_cron_job.py +++ b/webapp/new_cron_job.py @@ -70,9 +70,9 @@ def cron(): if ' ' in script.filename: script.filename = script.filename.replace(" ", "-") print(str(script.filename)) - - script.save(secure_filename(script.filename)) - script_file = os.path.join(scripts_dir, script.filename) + new_script_name = f"cron_{cron_name}_{script.filename}" + script.save(secure_filename(new_script_name)) + script_file = os.path.join(scripts_dir, new_script_name) os.chmod(script_file, mode=0o0755) From 653f99efdc8695603f3dd440784b703b6edd6b4d Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:54:37 -0500 Subject: [PATCH 025/168] Added okta event hook verification check to jawa_receiver.py jamf_webhook_handler() --- webhook/jawa_receiver.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/webhook/jawa_receiver.py b/webhook/jawa_receiver.py index 41ece16..9151f51 100644 --- a/webhook/jawa_receiver.py +++ b/webhook/jawa_receiver.py @@ -1,15 +1,16 @@ +from bin import okta_verification import flask from flask import session, request, redirect, url_for, render_template -from glob import escape +# from glob import escape import json import logging import os -import requests +# import requests import subprocess server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) -jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) -webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) +# webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) blueprint = flask.Blueprint('jawa_receiver', __name__, template_folder='templates') @@ -39,23 +40,27 @@ def run_script(webhook_data, webhook_name): subprocess.Popen([each_webhook['script'], f"{webhook_data}"]) -@blueprint.route('/hooks/', methods=['POST']) +@blueprint.route('/hooks/', methods=['POST', 'GET']) def jamf_webhook_handler(webhook_name): print(webhook_name) webhook_data = request.get_json() + if request.headers.get('x-okta-verification-challenge'): + print("This is an Okta verification challenge...") + return okta_verification.verify_new_webhook(request.headers.get('x-okta-verification-challenge')) + auth = request.authorization webhook_user = "null" webhook_pass = "null" if auth: # .get("username"): webhook_user = auth.get("username") webhook_pass = auth.get("password") - print(f"Webhook user: {webhook_user}\n" # todo: check username from new webhook.conf - f"Webhook password: {webhook_pass}\n" # todo: check password from new webhook.conf - f"Webhook name: {webhook_name}\n" - f"Webhook data: {webhook_data}") - print(webhook_data) - print(webhook_name) + # Debug + # print(f"Webhook user: {webhook_user}\n" + # f"Webhook password: {webhook_pass}\n" + # f"Webhook name: {webhook_name}\n" + # f"Webhook data: {webhook_data}") + if validate_webhook(webhook_data, webhook_name, webhook_user, webhook_pass): print(f"{webhook_name} validated!") run_script(webhook_data, webhook_name) From d1fd78a4615ee5d2c498b07625c05edf31900f7a Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:55:21 -0500 Subject: [PATCH 026/168] it's dangerous out there --- security/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 security/.gitkeep diff --git a/security/.gitkeep b/security/.gitkeep deleted file mode 100644 index e69de29..0000000 From b89ae868b1886acb795230c4f62cb1cf0e6d1433 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:56:20 -0500 Subject: [PATCH 027/168] Renamed jp_webhooks.json to webhooks.json --- webapp/delete_jp_webhook.py | 6 +++--- webapp/edit_jp_webhook.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/webapp/delete_jp_webhook.py b/webapp/delete_jp_webhook.py index f4d25b6..1acc0e9 100644 --- a/webapp/delete_jp_webhook.py +++ b/webapp/delete_jp_webhook.py @@ -16,7 +16,7 @@ verify_ssl = True server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) -jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) delete_jp = Blueprint('delete', __name__) @@ -24,7 +24,7 @@ @delete_jp.route('/delete', methods=['GET', 'POST']) def delete(): - jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) + jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) if not os.path.isfile(server_json_file): return render_template('setup.html', setup="setup", @@ -38,7 +38,7 @@ def delete(): jps_url=str(escape(session['url'])), username=str(escape(session['username']))) if 'username' in session: - with open(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')), 'r+') as fin: + with open(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')), 'r+') as fin: # content = fin.read() webhook_data = json.load(fin) # text = open(jp_webhooks_file, 'r+') diff --git a/webapp/edit_jp_webhook.py b/webapp/edit_jp_webhook.py index e37c04c..37a431a 100644 --- a/webapp/edit_jp_webhook.py +++ b/webapp/edit_jp_webhook.py @@ -11,7 +11,7 @@ send_from_directory, Blueprint, abort) server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) -jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'jp_webhooks.json')) +jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) edit_jp = Blueprint('edit', __name__) From 2aa936874b1a61db113d5cc7b8219bd05caff138 Mon Sep 17 00:00:00 2001 From: ball42 Date: Wed, 16 Jun 2021 02:58:00 -0500 Subject: [PATCH 028/168] /wizard now uses tags to organize Jamf Pro & Okta webhooks --- webapp.py | 156 +++++++++++++++++++++++------------------------------- 1 file changed, 66 insertions(+), 90 deletions(-) diff --git a/webapp.py b/webapp.py index 7fe6bd9..668e4a5 100644 --- a/webapp.py +++ b/webapp.py @@ -1,14 +1,13 @@ #!/usr/bin/python3 # encoding: utf-8 -import io -import os -import json +from collections import defaultdict +from flask import (Flask, request, render_template, + session, redirect, url_for, escape) import glob +import json import logging -from time import sleep +import os import requests -from flask import (Flask, request, render_template, - session, redirect, url_for, escape) from waitress import serve # Flask logging @@ -17,14 +16,8 @@ error_message = "" verify_ssl = True # Enables Jamf Pro SSL certificate verification -# global jamf_url -# global jamf_username -# global jamf_password -# global distilled_serial -app = Flask(__name__) - - # Initiate Flask +app = Flask(__name__) def main(): @@ -32,19 +25,16 @@ def main(): print(base_dir) environment_setup(base_dir) register_blueprints() - app.secret_key = "*" + app.secret_key = "untini" serve(app, url_scheme='https', host='0.0.0.0', port=8000) def environment_setup(project_dir): - global jp_file, okta_file, cron_file, server_json_file, scripts_directory - # webhook_file = os.path.join(project_dir, 'data', 'webhook.conf') - jp_file = os.path.join(project_dir, 'data', 'jp_webhooks.json') - okta_file = os.path.join(project_dir, 'data', 'okta_json.json') + global jp_file, cron_file, server_json_file, scripts_directory + jp_file = os.path.join(project_dir, 'data', 'webhooks.json') cron_file = os.path.join(project_dir, 'data', 'cron.json') server_json_file = os.path.join(project_dir, 'data', 'server.json') scripts_directory = os.path.join(project_dir, 'scripts') - # return webhook_file, jp_file, okta_file, cron_file, server_json_file, scripts_directory def register_blueprints(): @@ -80,26 +70,23 @@ def register_blueprints(): @app.route('/setup', methods=['GET', 'POST']) def setup(): if 'username' in session: - - # global jawa_address - if request.method == 'POST': server_url = request.form.get('address') jps_url = request.form.get('jss-lock') new_json = {} if server_url != '': new_json['jawa_address'] = server_url - print(server_url) + # print(server_url) if jps_url != '': new_json['jps_url'] = jps_url - print(new_json) + # print(new_json) if not os.path.isfile(server_json_file): with open(server_json_file, "w") as outfile: server_json = {'jawa_address': server_url, 'jps_url': jps_url} json.dump(server_json, outfile) elif os.path.isfile(server_json_file): with open(server_json_file, "w") as outfile: - server_json = [{'jawa_address': server_url, 'jps_url': jps_url}] + server_json = {'jawa_address': server_url, 'jps_url': jps_url} json.dump(server_json, outfile) with open(server_json_file, "r") as fin: data = json.load(fin) @@ -207,7 +194,7 @@ def index(): else: with open(server_json_file) as json_file: server_json = json.load(json_file) - print(server_json) + # print(server_json) if not 'jps_url' in server_json: return render_template('home.html') @@ -233,7 +220,7 @@ def home(): else: with open(server_json_file) as json_file: server_json = json.load(json_file) - print(server_json) + # print(server_json) if not 'jps_url' in server_json: return render_template('home.html') elif server_json['jps_url'] is None: @@ -254,51 +241,56 @@ def home(): def wizard(): with open(jp_file) as webhook_json: webhooks_installed = json.load(webhook_json) - webhook_json = webhooks_installed - - response = requests.get( - session['url'] + '/JSSResource/webhooks', - auth=(session['username'], session['password']), - headers={'Accept': 'application/json'}, - verify=verify_ssl) - - found_jamf_webhooks = response.json()['webhooks'] + jamf_pro_webhooks = [] + okta_webhooks = [] + for each_webhook in webhooks_installed: + data = defaultdict(lambda: "MISSING", each_webhook) + tag = data['tag'] + if tag == "jamfpro": + jamf_pro_webhooks.append(each_webhook) + elif tag == "okta": + okta_webhooks.append(each_webhook) + + # print(f"Full JP Webhook list: {jamf_pro_webhooks}") + + response = requests.get( + session['url'] + '/JSSResource/webhooks', + auth=(session['username'], session['password']), + headers={'Accept': 'application/json'}, + verify=verify_ssl) + + found_jamf_webhooks = response.json()['webhooks'] + + jamf_webhooks = [] + + x = 0 + while True: + try: + jamf_webhooks.append(found_jamf_webhooks[x]['name']) + x += 1 + str_error = None + except Exception as str_error: + pass + if str_error: + # sleep(2) + break + else: + continue - x = 0 - jamf_webhooks = [] - while True: - try: - jamf_webhooks.append(found_jamf_webhooks[x]['name']) - x += 1 - str_error = None - except Exception as str_error: - pass - if str_error: - sleep(2) - break - else: - continue - - for webhook in webhooks_installed: - if webhook['name'] in jamf_webhooks: - webhook_endpoint = '/JSSResource/webhooks/name/' - response = requests.get( - session['url'] + webhook_endpoint + webhook['name'], - auth=(session['username'], session['password']), - headers={'Accept': 'application/json'}, - verify=verify_ssl) + for webhook in webhooks_installed: + if webhook['name'] in jamf_webhooks: + webhook_endpoint = '/JSSResource/webhooks/name/' + response = requests.get( + session['url'] + webhook_endpoint + webhook['name'], + auth=(session['username'], session['password']), + headers={'Accept': 'application/json'}, + verify=verify_ssl) - response_json = response.json() + response_json = response.json() - jamf_event = response_json['webhook']['event'] - jamf_id = response_json['webhook']['id'] - print(webhook['script']) - script = webhook['script'] - # webhook_json.append({"name": webhook['name'], - # "jamf_id": jamf_id, - # "event": jamf_event, - # "script": script, - # "description": webhook['description']}) + jamf_event = response_json['webhook']['event'] + jamf_id = response_json['webhook']['id'] + script = webhook['script'] data = [] @@ -316,39 +308,23 @@ def wizard(): "script": script[1], "description": cron['description']}) - data = [] - - if not os.path.isfile(okta_file): - with open(okta_file, "w") as outfile: - json.dump(data, outfile) - - with open(okta_file) as okta_json: - oktas_installed = json.load(okta_json) - oktas_json = [] - for okta in oktas_installed: - script = okta['script'].rsplit('/', 1) - oktas_json.append({"name": okta['name'], - "event": okta['okta_event'], - "script": script[1]}) - webhook_url = session['url'] if not webhook_json: webhook_json = '' if not crons_json: crons_json = '' - if not oktas_json: - oktas_json = '' + # if not oktas_json: + # oktas_json = '' - if webhook_json == crons_json == oktas_json: + if webhook_json == crons_json: return redirect(url_for('first_automation')) - return render_template( 'wizard.html', webhook_url=webhook_url, - webhooks_installed=webhook_json, + webhooks_installed=jamf_pro_webhooks, crons_installed=crons_json, - oktas_installed=oktas_json, + oktas_installed=okta_webhooks, login="true", username=str(escape(session['username']))) From fac208b6778c444ce8b1ccb74964a9dbb0e4ea9a Mon Sep 17 00:00:00 2001 From: ball42 Date: Sat, 26 Jun 2021 16:34:25 -0500 Subject: [PATCH 029/168] Removing a comment that referenced the old webhook.conf file to avoid confusion. --- webhook/jawa_receiver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/webhook/jawa_receiver.py b/webhook/jawa_receiver.py index 9151f51..2bf2bdd 100644 --- a/webhook/jawa_receiver.py +++ b/webhook/jawa_receiver.py @@ -10,7 +10,6 @@ server_json_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'server.json')) jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) -# webhook_conf = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhook.conf')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) blueprint = flask.Blueprint('jawa_receiver', __name__, template_folder='templates') From 1f0caa08c4f253b09f0af9118bb9b8d95c51b297 Mon Sep 17 00:00:00 2001 From: ball42 Date: Sat, 26 Jun 2021 16:37:05 -0500 Subject: [PATCH 030/168] renamed webhook_handler() as it is not limited to jamf --- webhook/jawa_receiver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/jawa_receiver.py b/webhook/jawa_receiver.py index 2bf2bdd..6f1e21d 100644 --- a/webhook/jawa_receiver.py +++ b/webhook/jawa_receiver.py @@ -40,7 +40,7 @@ def run_script(webhook_data, webhook_name): @blueprint.route('/hooks/', methods=['POST', 'GET']) -def jamf_webhook_handler(webhook_name): +def webhook_handler(webhook_name): print(webhook_name) webhook_data = request.get_json() if request.headers.get('x-okta-verification-challenge'): From 8fea49fb62790c1de3669dd63d867cfe07a907c0 Mon Sep 17 00:00:00 2001 From: ball42 Date: Mon, 28 Jun 2021 23:59:53 -0500 Subject: [PATCH 031/168] Added "alternate JPS" option to address issue #11 requesting support for multiple JPS entries --- templates/home.html | 7 + templates/setup.html | 107 ++++++------ templates/wizard.html | 387 ++++++++++++++++++++++-------------------- webapp.py | 39 ++++- 4 files changed, 300 insertions(+), 240 deletions(-) diff --git a/templates/home.html b/templates/home.html index 5a839c7..b5cc3ba 100644 --- a/templates/home.html +++ b/templates/home.html @@ -12,6 +12,13 @@

JAWA

Please log in.

{% if jsslock %} + {% if jps_url2 %} + Choose your JPS instance: + + {% endif %} {% else %}

Jamf Pro URL: {{ input('url') }}

{% endif %} diff --git a/templates/setup.html b/templates/setup.html index d599c3b..d13b94c 100644 --- a/templates/setup.html +++ b/templates/setup.html @@ -1,65 +1,76 @@ {% macro input(name, value='', type='text', size=20) -%} - + {%- endmacro %} - + - {% extends "setuplayout.html" %} - {% block content %} -
+{% extends "setuplayout.html" %} +{% block content %} +
-

Server Setup

+

Server Setup

-

You seek to use the JAWA!

- Please read the documentation prior to using the JAWA!

+

You seek to use the JAWA!

+ Please read the documentation prior to using the JAWA!

- Notes:
- - You must include the protocol (https://) in the address.
- - Address must have inbound/outbound communication with your Jamf Pro.
-


- Lock your JAWA to the JPS server: {{jps_url}}

+ Notes:
+ - You must include the protocol (https://) in the address.
+ - Address must have inbound/outbound communication with your Jamf Pro.
+


+ Lock your JAWA to the JPS + server: {{ jps_url }}
+ {% if not jps_url2 %} + Add an alternate JPS server: + + {% else %} + Add an alternate JPS + server: + + {% endif %} +

- JAWA Server Address:
- (i.e. https://jawa.company.com)

- -
-
-
-
-
- -
+ JAWA Server Address:
+ (i.e. https://jawa.company.com)

+ +
+
+
+
+
+ +
+
+
-
-
-
-
-
-
- To clean up unused scripts: Cleanup. - - {% endblock %} -
+
+
+
+
+ To clean up unused scripts: Cleanup. + +{% endblock %} +
\ No newline at end of file diff --git a/templates/wizard.html b/templates/wizard.html index 1bc563e..0b5d1e1 100644 --- a/templates/wizard.html +++ b/templates/wizard.html @@ -1,199 +1,218 @@ {% macro input(name, value='', type='text', size=20) -%} - + {%- endmacro %} - + - {% extends "wizardlayout.html" %} - {% block content %} -
- -

JAWA Dashboard

- - What would you like to do?

- Create a New Webhook

- - Create a New Timed Automation


- -
- -
-
-
-

Configured Jamf Pro Webhooks:

- - - - - - - - {% for row in webhooks_installed %} - - - - - - - {% endfor %} -
NameEventScriptDescription
{{row.name}} (View in Jamf){{row.event}}{{row.script}}{{row.description}}
- Adjust Webhooks -


-
-
-
- - -
-
-
-

Configured Timed Automations:

- - - - - - - - - {% for row in crons_installed %} - - - - - - - {% endfor %} -
NameFrequencyScriptDescription
{{row.name}}{{row.frequency}}{{row.script}}{{row.description}}
- Adjust Timed Automations -


-
-
-
- -
-
-
-

Configured Okta Hooks:

- - - - - - - - {% for row in oktas_installed %} - - - - - - {% endfor %} -
NameEventScript
{{row.name}}{{row.okta_event}}{{row.script}}
- Modify Okta Webhooks -


-
- -
-
- -



- - Setup Options:

- Configure the JAWA



- - {% endblock %} -
+{% extends "wizardlayout.html" %} +{% block content %} +
+ +

JAWA Dashboard

+ Active JPS: {{ session['url'] }}

+ What would you like to do?

+ Create a New Webhook

+ + Create a New Timed Automation


+ +
+ +
+
+
+

Configured Jamf Pro Webhooks:

+ + + + + + + + {% for row in webhooks_installed %} + {% if row.url == session['url'] %} + + + + + + + {% endif %} + {% endfor %} +
NameEventScriptDescription
{{ row.name }} (View in Jamf) + {{ row.event }}{{ row.script }}{{ row.description }}
+ Adjust Webhooks +


+
+
+
+ + +
+
+
+

Configured Timed Automations:

+ + + + + + + + + {% for row in crons_installed %} + + + + + + + {% endfor %} +
NameFrequencyScriptDescription
{{ row.name }}{{ row.frequency }}{{ row.script }}{{ row.description }}
+ Adjust Timed Automations +


+
+
+
+ +
+
+
+

Configured Okta Hooks:

+ + + + + + + + {% for row in oktas_installed %} + + + + + + {% endfor %} +
NameEventScript
{{ row.name }}{{ row.okta_event }}{{ row.script }}
+ Modify Okta Webhooks +


+
+ +
+
+ +



+ + Setup Options:

+ Configure the JAWA


+
+ +{% endblock %} +
\ No newline at end of file diff --git a/webapp.py b/webapp.py index 668e4a5..9376500 100644 --- a/webapp.py +++ b/webapp.py @@ -73,6 +73,9 @@ def setup(): if request.method == 'POST': server_url = request.form.get('address') jps_url = request.form.get('jss-lock') + jps2_check = request.form.get('alternate-jps') + print(jps2_check) + jps_url2 = request.form.get('alternate') new_json = {} if server_url != '': new_json['jawa_address'] = server_url @@ -82,11 +85,11 @@ def setup(): # print(new_json) if not os.path.isfile(server_json_file): with open(server_json_file, "w") as outfile: - server_json = {'jawa_address': server_url, 'jps_url': jps_url} + server_json = {'jawa_address': server_url, 'jps_url': jps_url, 'alternate_jps': jps_url2} json.dump(server_json, outfile) elif os.path.isfile(server_json_file): with open(server_json_file, "w") as outfile: - server_json = {'jawa_address': server_url, 'jps_url': jps_url} + server_json = {'jawa_address': server_url, 'jps_url': jps_url, 'alternate_jps': jps_url2} json.dump(server_json, outfile) with open(server_json_file, "r") as fin: data = json.load(fin) @@ -98,8 +101,17 @@ def setup(): webhooks="success", username=str(escape(session['username']))) else: + if not os.path.isfile(server_json_file): + with open(server_json_file, "w") as outfile: + server_json = {'jawa_address': '', 'jps_url': '', 'alternate_jps': ''} + json.dump(server_json, outfile) + with open(server_json_file, "r") as fin: + server_json = json.load(fin) + jps_url2 = server_json['alternate_jps'] + jawa_url = server_json['jawa_address'] return render_template('setup.html', - login="false", jps_url=str(escape(session['url']))) + login="false", jps_url=str(escape(session['url'])), jps_url2=jps_url2, + jawa_url=jawa_url) else: return render_template('home.html', login="false") @@ -142,12 +154,14 @@ def login(): if os.path.isfile(server_json_file): with open(server_json_file) as json_file: server_json = json.load(json_file) - - if server_json.get('jps_url', 0): - if server_json['jps_url'] != None and len(server_json['jps_url']) != 0: - session['url'] = str(server_json['jps_url']) + if request.form['active_url'] != '': + session['url'] = str(request.form.get('active_url')) else: - session['url'] = request.form['url'] + if server_json.get('jps_url', 0): + if server_json['jps_url'] is not None and len(server_json['jps_url']) != 0: + session['url'] = str(server_json['jps_url']) + else: + session['url'] = request.form['url'] else: session['url'] = request.form['url'] session['username'] = request.form['username'] @@ -203,6 +217,15 @@ def index(): elif len(server_json['jps_url']) == 0: return render_template('home.html') else: + if not 'alternate_jps' in server_json: + return render_template('home.html') + + if server_json['alternate_jps'] != "": + return render_template('home.html', + jps_url=server_json['jps_url'], + jps_url2=server_json['alternate_jps'], + welcome="true", jsslock="true") + session['url'] = server_json['jps_url'] return render_template('home.html', jps_url=str(escape(session['url'])), From 56bd05d3a8234dae97adae8463d2e2e91a012313 Mon Sep 17 00:00:00 2001 From: ball42 Date: Tue, 29 Jun 2021 00:19:26 -0500 Subject: [PATCH 032/168] Using existing primary jps_url for the "Lock" feature in /setup when logged in to alternate JPS. To prevent silliness --- webapp.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webapp.py b/webapp.py index 9376500..2deff6e 100644 --- a/webapp.py +++ b/webapp.py @@ -108,9 +108,13 @@ def setup(): with open(server_json_file, "r") as fin: server_json = json.load(fin) jps_url2 = server_json['alternate_jps'] + if jps_url2 == str(escape(session['url'])): + primary_jps = server_json['jps_url'] + else: + primary_jps = str(escape(session['url'])) jawa_url = server_json['jawa_address'] return render_template('setup.html', - login="false", jps_url=str(escape(session['url'])), jps_url2=jps_url2, + login="false", jps_url=primary_jps, jps_url2=jps_url2, jawa_url=jawa_url) else: return render_template('home.html', From 5ccddb1be92d4ba69484dd1a2b93613be80754a1 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 1 Jul 2021 01:19:19 -0500 Subject: [PATCH 033/168] resource files need a home --- resources/files/.gitignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/files/.gitignore diff --git a/resources/files/.gitignore b/resources/files/.gitignore new file mode 100644 index 0000000..e69de29 From e6e195da37af519e83361f5b38e7426b84ed02bd Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 1 Jul 2021 01:25:53 -0500 Subject: [PATCH 034/168] CSS and shared layout file for log & resources. --- static/css/account.css | 101 + static/css/bootstrap.css | 10412 ++++++++++++++++++++++++++++++++ static/css/fonts.css | 384 ++ static/css/main.css | 155 +- static/css/nav.css | 81 + static/css/site.css | 201 + templates/shared/_layout.html | 89 + 7 files changed, 11359 insertions(+), 64 deletions(-) create mode 100644 static/css/account.css create mode 100644 static/css/bootstrap.css create mode 100644 static/css/fonts.css create mode 100644 static/css/nav.css create mode 100644 static/css/site.css create mode 100644 templates/shared/_layout.html diff --git a/static/css/account.css b/static/css/account.css new file mode 100644 index 0000000..dfd62cc --- /dev/null +++ b/static/css/account.css @@ -0,0 +1,101 @@ + +.form-container { + padding: 25px; +} + +form.account-form > * { + margin-top: 5px; + margin-bottom: 5px; +} + +form.account-form h1 { + text-align: center; +} + +form.account-form button { + float: right; +} + +form.account-form select { + padding: 0; +} + +form.account-form { + margin-top: auto; + margin-left: auto; + margin-right: auto; + max-width: 450px; + background-color: #c9c9c9; + border: 1px solid gray; + border-radius: 5px; + padding: 10px; +} + +form.files-form { + margin-top: auto; + margin-left: auto; + margin-right: auto; + + max-width: 1000px; + background-color: #c9c9c9; + border: 1px solid gray; + border-radius: 5px; + padding: 10px; +} +.error-msg { + color: #b30000; + text-align: center; +} + +.diagnosis { + padding: 400px; + color: #2b6ca8; + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 40px; + margin-right: 40px; + background-color: pink; +} + +.success-wrap { + + overflow: hidden; + border-left:1em solid transparent; + border-right:1em solid transparent; + text-overflow: ellipsis; +} + +.success-wrap:hover { + overflow: visible; +} + +.success-form { + margin-top: auto; + margin-left: auto; + margin-right: auto; + max-width: 1000px; + background-color: #c9c9c9; + border: 1px solid gray; + border-radius: 5px; + padding: 10px; +} + +.button-align { + align-items: right; +} + +.test-banner { + background-color: #ececec; +} + +.body { + padding-top: 60px; +} + +@media (max-width: 979px) { + body { + padding-top: 0px; + } +} + diff --git a/static/css/bootstrap.css b/static/css/bootstrap.css new file mode 100644 index 0000000..b909727 --- /dev/null +++ b/static/css/bootstrap.css @@ -0,0 +1,10412 @@ +/*! + * Bootstrap v4.5.2 (https://getbootstrap.com/) + * Copyright 2011-2020 The Bootstrap Authors + * Copyright 2011-2020 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +:root { + --blue: #007bff; + --indigo: #6610f2; + --purple: #6f42c1; + --pink: #e83e8c; + --red: #dc3545; + --orange: #fd7e14; + --yellow: #ffc107; + --green: #28a745; + --teal: #20c997; + --cyan: #17a2b8; + --white: #fff; + --gray: #6c757d; + --gray-dark: #343a40; + --primary: #007bff; + --secondary: #6c757d; + --success: #28a745; + --info: #17a2b8; + --warning: #ffc107; + --danger: #dc3545; + --light: #f8f9fa; + --dark: #343a40; + --breakpoint-xs: 0; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 992px; + --breakpoint-xl: 1200px; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace +} + +*, ::after, ::before { + box-sizing: border-box +} + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent +} + +article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff +} + +[tabindex="-1"]:focus:not(:focus-visible) { + outline: 0 !important +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: .5rem +} + +p { + margin-top: 0; + margin-bottom: 1rem +} + +abbr[data-original-title], abbr[title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit +} + +dl, ol, ul { + margin-top: 0; + margin-bottom: 1rem +} + +ol ol, ol ul, ul ol, ul ul { + margin-bottom: 0 +} + +dt { + font-weight: 700 +} + +dd { + margin-bottom: .5rem; + margin-left: 0 +} + +blockquote { + margin: 0 0 1rem +} + +b, strong { + font-weight: bolder +} + +small { + font-size: 80% +} + +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline +} + +sub { + bottom: -.25em +} + +sup { + top: -.5em +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent +} + +a:hover { + color: #0056b3; + text-decoration: underline +} + +a:not([href]):not([class]) { + color: inherit; + text-decoration: none +} + +a:not([href]):not([class]):hover { + color: inherit; + text-decoration: none +} + +code, kbd, pre, samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 1em +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar +} + +figure { + margin: 0 0 1rem +} + +img { + vertical-align: middle; + border-style: none +} + +svg { + overflow: hidden; + vertical-align: middle +} + +table { + border-collapse: collapse +} + +caption { + padding-top: .75rem; + padding-bottom: .75rem; + color: #6c757d; + text-align: left; + caption-side: bottom +} + +th { + text-align: inherit +} + +label { + display: inline-block; + margin-bottom: .5rem +} + +button { + border-radius: 0 +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color +} + +button, input, optgroup, select, textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit +} + +button, input { + overflow: visible +} + +button, select { + text-transform: none +} + +[role=button] { + cursor: pointer +} + +select { + word-wrap: normal +} + +[type=button], [type=reset], [type=submit], button { + -webkit-appearance: button +} + +[type=button]:not(:disabled), [type=reset]:not(:disabled), [type=submit]:not(:disabled), button:not(:disabled) { + cursor: pointer +} + +[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner { + padding: 0; + border-style: none +} + +input[type=checkbox], input[type=radio] { + box-sizing: border-box; + padding: 0 +} + +textarea { + overflow: auto; + resize: vertical +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0 +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal +} + +progress { + vertical-align: baseline +} + +[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button { + height: auto +} + +[type=search] { + outline-offset: -2px; + -webkit-appearance: none +} + +[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button +} + +output { + display: inline-block +} + +summary { + display: list-item; + cursor: pointer +} + +template { + display: none +} + +[hidden] { + display: none !important +} + +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + margin-bottom: .5rem; + font-weight: 500; + line-height: 1.2 +} + +.h1, h1 { + font-size: 2.5rem +} + +.h2, h2 { + font-size: 2rem +} + +.h3, h3 { + font-size: 1.75rem +} + +.h4, h4 { + font-size: 1.5rem +} + +.h5, h5 { + font-size: 1.25rem +} + +.h6, h6 { + font-size: 1rem +} + +.lead { + font-size: 1.25rem; + font-weight: 300 +} + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.2 +} + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.2 +} + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.2 +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.2 +} + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, .1) +} + +.small, small { + font-size: 80%; + font-weight: 400 +} + +.mark, mark { + padding: .2em; + background-color: #fcf8e3 +} + +.list-unstyled { + padding-left: 0; + list-style: none +} + +.list-inline { + padding-left: 0; + list-style: none +} + +.list-inline-item { + display: inline-block +} + +.list-inline-item:not(:last-child) { + margin-right: .5rem +} + +.initialism { + font-size: 90%; + text-transform: uppercase +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem +} + +.blockquote-footer { + display: block; + font-size: 80%; + color: #6c757d +} + +.blockquote-footer::before { + content: "\2014\00A0" +} + +.img-fluid { + max-width: 100%; + height: auto +} + +.img-thumbnail { + padding: .25rem; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: .25rem; + max-width: 100%; + height: auto +} + +.figure { + display: inline-block +} + +.figure-img { + margin-bottom: .5rem; + line-height: 1 +} + +.figure-caption { + font-size: 90%; + color: #6c757d +} + +code { + font-size: 87.5%; + color: #e83e8c; + word-wrap: break-word +} + +a > code { + color: inherit +} + +kbd { + padding: .2rem .4rem; + font-size: 87.5%; + color: #fff; + background-color: #212529; + border-radius: .2rem +} + +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700 +} + +pre { + display: block; + font-size: 87.5%; + color: #212529 +} + +pre code { + font-size: inherit; + color: inherit; + word-break: normal +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll +} + +.container, .container-fluid, .container-lg, .container-md, .container-sm, .container-xl { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; + display: flex; + justify-content: space-around; +} + +@media (min-width: 576px) { + .container, .container-sm { + max-width: 540px + } +} + +@media (min-width: 768px) { + .container, .container-md, .container-sm { + max-width: 720px + } +} + +@media (min-width: 992px) { + .container, .container-lg, .container-md, .container-sm { + max-width: 960px + } +} + +@media (min-width: 1200px) { + .container, .container-lg, .container-md, .container-sm, .container-xl { + max-width: 1140px + } +} + +.row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px +} + +.no-gutters { + margin-right: 0; + margin-left: 0 +} + +.no-gutters > .col, .no-gutters > [class*=col-] { + padding-right: 0; + padding-left: 0 +} + +.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto { + position: relative; + width: 100%; + padding-right: 15px; + padding-left: 15px +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100% +} + +.row-cols-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% +} + +.row-cols-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% +} + +.row-cols-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% +} + +.row-cols-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% +} + +.row-cols-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20% +} + +.row-cols-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% +} + +.col-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100% +} + +.col-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333% +} + +.col-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% +} + +.col-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% +} + +.col-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% +} + +.col-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667% +} + +.col-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% +} + +.col-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333% +} + +.col-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667% +} + +.col-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75% +} + +.col-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333% +} + +.col-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667% +} + +.col-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% +} + +.order-first { + -ms-flex-order: -1; + order: -1 +} + +.order-last { + -ms-flex-order: 13; + order: 13 +} + +.order-0 { + -ms-flex-order: 0; + order: 0 +} + +.order-1 { + -ms-flex-order: 1; + order: 1 +} + +.order-2 { + -ms-flex-order: 2; + order: 2 +} + +.order-3 { + -ms-flex-order: 3; + order: 3 +} + +.order-4 { + -ms-flex-order: 4; + order: 4 +} + +.order-5 { + -ms-flex-order: 5; + order: 5 +} + +.order-6 { + -ms-flex-order: 6; + order: 6 +} + +.order-7 { + -ms-flex-order: 7; + order: 7 +} + +.order-8 { + -ms-flex-order: 8; + order: 8 +} + +.order-9 { + -ms-flex-order: 9; + order: 9 +} + +.order-10 { + -ms-flex-order: 10; + order: 10 +} + +.order-11 { + -ms-flex-order: 11; + order: 11 +} + +.order-12 { + -ms-flex-order: 12; + order: 12 +} + +.offset-1 { + margin-left: 8.333333% +} + +.offset-2 { + margin-left: 16.666667% +} + +.offset-3 { + margin-left: 25% +} + +.offset-4 { + margin-left: 33.333333% +} + +.offset-5 { + margin-left: 41.666667% +} + +.offset-6 { + margin-left: 50% +} + +.offset-7 { + margin-left: 58.333333% +} + +.offset-8 { + margin-left: 66.666667% +} + +.offset-9 { + margin-left: 75% +} + +.offset-10 { + margin-left: 83.333333% +} + +.offset-11 { + margin-left: 91.666667% +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100% + } + + .row-cols-sm-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% + } + + .row-cols-sm-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% + } + + .row-cols-sm-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% + } + + .row-cols-sm-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% + } + + .row-cols-sm-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20% + } + + .row-cols-sm-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% + } + + .col-sm-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100% + } + + .col-sm-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333% + } + + .col-sm-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% + } + + .col-sm-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% + } + + .col-sm-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% + } + + .col-sm-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667% + } + + .col-sm-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% + } + + .col-sm-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333% + } + + .col-sm-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667% + } + + .col-sm-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75% + } + + .col-sm-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333% + } + + .col-sm-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667% + } + + .col-sm-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% + } + + .order-sm-first { + -ms-flex-order: -1; + order: -1 + } + + .order-sm-last { + -ms-flex-order: 13; + order: 13 + } + + .order-sm-0 { + -ms-flex-order: 0; + order: 0 + } + + .order-sm-1 { + -ms-flex-order: 1; + order: 1 + } + + .order-sm-2 { + -ms-flex-order: 2; + order: 2 + } + + .order-sm-3 { + -ms-flex-order: 3; + order: 3 + } + + .order-sm-4 { + -ms-flex-order: 4; + order: 4 + } + + .order-sm-5 { + -ms-flex-order: 5; + order: 5 + } + + .order-sm-6 { + -ms-flex-order: 6; + order: 6 + } + + .order-sm-7 { + -ms-flex-order: 7; + order: 7 + } + + .order-sm-8 { + -ms-flex-order: 8; + order: 8 + } + + .order-sm-9 { + -ms-flex-order: 9; + order: 9 + } + + .order-sm-10 { + -ms-flex-order: 10; + order: 10 + } + + .order-sm-11 { + -ms-flex-order: 11; + order: 11 + } + + .order-sm-12 { + -ms-flex-order: 12; + order: 12 + } + + .offset-sm-0 { + margin-left: 0 + } + + .offset-sm-1 { + margin-left: 8.333333% + } + + .offset-sm-2 { + margin-left: 16.666667% + } + + .offset-sm-3 { + margin-left: 25% + } + + .offset-sm-4 { + margin-left: 33.333333% + } + + .offset-sm-5 { + margin-left: 41.666667% + } + + .offset-sm-6 { + margin-left: 50% + } + + .offset-sm-7 { + margin-left: 58.333333% + } + + .offset-sm-8 { + margin-left: 66.666667% + } + + .offset-sm-9 { + margin-left: 75% + } + + .offset-sm-10 { + margin-left: 83.333333% + } + + .offset-sm-11 { + margin-left: 91.666667% + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100% + } + + .row-cols-md-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% + } + + .row-cols-md-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% + } + + .row-cols-md-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% + } + + .row-cols-md-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% + } + + .row-cols-md-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20% + } + + .row-cols-md-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% + } + + .col-md-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100% + } + + .col-md-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333% + } + + .col-md-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% + } + + .col-md-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% + } + + .col-md-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% + } + + .col-md-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667% + } + + .col-md-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% + } + + .col-md-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333% + } + + .col-md-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667% + } + + .col-md-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75% + } + + .col-md-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333% + } + + .col-md-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667% + } + + .col-md-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% + } + + .order-md-first { + -ms-flex-order: -1; + order: -1 + } + + .order-md-last { + -ms-flex-order: 13; + order: 13 + } + + .order-md-0 { + -ms-flex-order: 0; + order: 0 + } + + .order-md-1 { + -ms-flex-order: 1; + order: 1 + } + + .order-md-2 { + -ms-flex-order: 2; + order: 2 + } + + .order-md-3 { + -ms-flex-order: 3; + order: 3 + } + + .order-md-4 { + -ms-flex-order: 4; + order: 4 + } + + .order-md-5 { + -ms-flex-order: 5; + order: 5 + } + + .order-md-6 { + -ms-flex-order: 6; + order: 6 + } + + .order-md-7 { + -ms-flex-order: 7; + order: 7 + } + + .order-md-8 { + -ms-flex-order: 8; + order: 8 + } + + .order-md-9 { + -ms-flex-order: 9; + order: 9 + } + + .order-md-10 { + -ms-flex-order: 10; + order: 10 + } + + .order-md-11 { + -ms-flex-order: 11; + order: 11 + } + + .order-md-12 { + -ms-flex-order: 12; + order: 12 + } + + .offset-md-0 { + margin-left: 0 + } + + .offset-md-1 { + margin-left: 8.333333% + } + + .offset-md-2 { + margin-left: 16.666667% + } + + .offset-md-3 { + margin-left: 25% + } + + .offset-md-4 { + margin-left: 33.333333% + } + + .offset-md-5 { + margin-left: 41.666667% + } + + .offset-md-6 { + margin-left: 50% + } + + .offset-md-7 { + margin-left: 58.333333% + } + + .offset-md-8 { + margin-left: 66.666667% + } + + .offset-md-9 { + margin-left: 75% + } + + .offset-md-10 { + margin-left: 83.333333% + } + + .offset-md-11 { + margin-left: 91.666667% + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100% + } + + .row-cols-lg-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% + } + + .row-cols-lg-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% + } + + .row-cols-lg-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% + } + + .row-cols-lg-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% + } + + .row-cols-lg-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20% + } + + .row-cols-lg-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% + } + + .col-lg-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100% + } + + .col-lg-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333% + } + + .col-lg-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% + } + + .col-lg-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% + } + + .col-lg-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% + } + + .col-lg-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667% + } + + .col-lg-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% + } + + .col-lg-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333% + } + + .col-lg-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667% + } + + .col-lg-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75% + } + + .col-lg-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333% + } + + .col-lg-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667% + } + + .col-lg-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% + } + + .order-lg-first { + -ms-flex-order: -1; + order: -1 + } + + .order-lg-last { + -ms-flex-order: 13; + order: 13 + } + + .order-lg-0 { + -ms-flex-order: 0; + order: 0 + } + + .order-lg-1 { + -ms-flex-order: 1; + order: 1 + } + + .order-lg-2 { + -ms-flex-order: 2; + order: 2 + } + + .order-lg-3 { + -ms-flex-order: 3; + order: 3 + } + + .order-lg-4 { + -ms-flex-order: 4; + order: 4 + } + + .order-lg-5 { + -ms-flex-order: 5; + order: 5 + } + + .order-lg-6 { + -ms-flex-order: 6; + order: 6 + } + + .order-lg-7 { + -ms-flex-order: 7; + order: 7 + } + + .order-lg-8 { + -ms-flex-order: 8; + order: 8 + } + + .order-lg-9 { + -ms-flex-order: 9; + order: 9 + } + + .order-lg-10 { + -ms-flex-order: 10; + order: 10 + } + + .order-lg-11 { + -ms-flex-order: 11; + order: 11 + } + + .order-lg-12 { + -ms-flex-order: 12; + order: 12 + } + + .offset-lg-0 { + margin-left: 0 + } + + .offset-lg-1 { + margin-left: 8.333333% + } + + .offset-lg-2 { + margin-left: 16.666667% + } + + .offset-lg-3 { + margin-left: 25% + } + + .offset-lg-4 { + margin-left: 33.333333% + } + + .offset-lg-5 { + margin-left: 41.666667% + } + + .offset-lg-6 { + margin-left: 50% + } + + .offset-lg-7 { + margin-left: 58.333333% + } + + .offset-lg-8 { + margin-left: 66.666667% + } + + .offset-lg-9 { + margin-left: 75% + } + + .offset-lg-10 { + margin-left: 83.333333% + } + + .offset-lg-11 { + margin-left: 91.666667% + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100% + } + + .row-cols-xl-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% + } + + .row-cols-xl-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% + } + + .row-cols-xl-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% + } + + .row-cols-xl-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% + } + + .row-cols-xl-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20% + } + + .row-cols-xl-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% + } + + .col-xl-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100% + } + + .col-xl-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333% + } + + .col-xl-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667% + } + + .col-xl-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25% + } + + .col-xl-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333% + } + + .col-xl-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667% + } + + .col-xl-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50% + } + + .col-xl-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333% + } + + .col-xl-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667% + } + + .col-xl-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75% + } + + .col-xl-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333% + } + + .col-xl-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667% + } + + .col-xl-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100% + } + + .order-xl-first { + -ms-flex-order: -1; + order: -1 + } + + .order-xl-last { + -ms-flex-order: 13; + order: 13 + } + + .order-xl-0 { + -ms-flex-order: 0; + order: 0 + } + + .order-xl-1 { + -ms-flex-order: 1; + order: 1 + } + + .order-xl-2 { + -ms-flex-order: 2; + order: 2 + } + + .order-xl-3 { + -ms-flex-order: 3; + order: 3 + } + + .order-xl-4 { + -ms-flex-order: 4; + order: 4 + } + + .order-xl-5 { + -ms-flex-order: 5; + order: 5 + } + + .order-xl-6 { + -ms-flex-order: 6; + order: 6 + } + + .order-xl-7 { + -ms-flex-order: 7; + order: 7 + } + + .order-xl-8 { + -ms-flex-order: 8; + order: 8 + } + + .order-xl-9 { + -ms-flex-order: 9; + order: 9 + } + + .order-xl-10 { + -ms-flex-order: 10; + order: 10 + } + + .order-xl-11 { + -ms-flex-order: 11; + order: 11 + } + + .order-xl-12 { + -ms-flex-order: 12; + order: 12 + } + + .offset-xl-0 { + margin-left: 0 + } + + .offset-xl-1 { + margin-left: 8.333333% + } + + .offset-xl-2 { + margin-left: 16.666667% + } + + .offset-xl-3 { + margin-left: 25% + } + + .offset-xl-4 { + margin-left: 33.333333% + } + + .offset-xl-5 { + margin-left: 41.666667% + } + + .offset-xl-6 { + margin-left: 50% + } + + .offset-xl-7 { + margin-left: 58.333333% + } + + .offset-xl-8 { + margin-left: 66.666667% + } + + .offset-xl-9 { + margin-left: 75% + } + + .offset-xl-10 { + margin-left: 83.333333% + } + + .offset-xl-11 { + margin-left: 91.666667% + } +} + +.table { + width: 100%; + margin-bottom: 1rem; + color: #212529 +} + +.table td, .table th { + padding: .75rem; + vertical-align: top; + border-top: 1px solid #dee2e6 +} + +.table thead th { + vertical-align: bottom; + border-bottom: 2px solid #dee2e6 +} + +.table tbody + tbody { + border-top: 2px solid #dee2e6 +} + +.table-sm td, .table-sm th { + padding: .3rem +} + +.table-bordered { + border: 1px solid #dee2e6 +} + +.table-bordered td, .table-bordered th { + border: 1px solid #dee2e6 +} + +.table-bordered thead td, .table-bordered thead th { + border-bottom-width: 2px +} + +.table-borderless tbody + tbody, .table-borderless td, .table-borderless th, .table-borderless thead th { + border: 0 +} + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, .05) +} + +.table-hover tbody tr:hover { + color: #212529; + background-color: rgba(0, 0, 0, .075) +} + +.table-primary, .table-primary > td, .table-primary > th { + background-color: #b8daff +} + +.table-primary tbody + tbody, .table-primary td, .table-primary th, .table-primary thead th { + border-color: #7abaff +} + +.table-hover .table-primary:hover { + background-color: #9fcdff +} + +.table-hover .table-primary:hover > td, .table-hover .table-primary:hover > th { + background-color: #9fcdff +} + +.table-secondary, .table-secondary > td, .table-secondary > th { + background-color: #d6d8db +} + +.table-secondary tbody + tbody, .table-secondary td, .table-secondary th, .table-secondary thead th { + border-color: #b3b7bb +} + +.table-hover .table-secondary:hover { + background-color: #c8cbcf +} + +.table-hover .table-secondary:hover > td, .table-hover .table-secondary:hover > th { + background-color: #c8cbcf +} + +.table-success, .table-success > td, .table-success > th { + background-color: #c3e6cb +} + +.table-success tbody + tbody, .table-success td, .table-success th, .table-success thead th { + border-color: #8fd19e +} + +.table-hover .table-success:hover { + background-color: #b1dfbb +} + +.table-hover .table-success:hover > td, .table-hover .table-success:hover > th { + background-color: #b1dfbb +} + +.table-info, .table-info > td, .table-info > th { + background-color: #bee5eb +} + +.table-info tbody + tbody, .table-info td, .table-info th, .table-info thead th { + border-color: #86cfda +} + +.table-hover .table-info:hover { + background-color: #abdde5 +} + +.table-hover .table-info:hover > td, .table-hover .table-info:hover > th { + background-color: #abdde5 +} + +.table-warning, .table-warning > td, .table-warning > th { + background-color: #ffeeba +} + +.table-warning tbody + tbody, .table-warning td, .table-warning th, .table-warning thead th { + border-color: #ffdf7e +} + +.table-hover .table-warning:hover { + background-color: #ffe8a1 +} + +.table-hover .table-warning:hover > td, .table-hover .table-warning:hover > th { + background-color: #ffe8a1 +} + +.table-danger, .table-danger > td, .table-danger > th { + background-color: #f5c6cb +} + +.table-danger tbody + tbody, .table-danger td, .table-danger th, .table-danger thead th { + border-color: #ed969e +} + +.table-hover .table-danger:hover { + background-color: #f1b0b7 +} + +.table-hover .table-danger:hover > td, .table-hover .table-danger:hover > th { + background-color: #f1b0b7 +} + +.table-light, .table-light > td, .table-light > th { + background-color: #fdfdfe +} + +.table-light tbody + tbody, .table-light td, .table-light th, .table-light thead th { + border-color: #fbfcfc +} + +.table-hover .table-light:hover { + background-color: #ececf6 +} + +.table-hover .table-light:hover > td, .table-hover .table-light:hover > th { + background-color: #ececf6 +} + +.table-dark, .table-dark > td, .table-dark > th { + background-color: #c6c8ca +} + +.table-dark tbody + tbody, .table-dark td, .table-dark th, .table-dark thead th { + border-color: #95999c +} + +.table-hover .table-dark:hover { + background-color: #b9bbbe +} + +.table-hover .table-dark:hover > td, .table-hover .table-dark:hover > th { + background-color: #b9bbbe +} + +.table-active, .table-active > td, .table-active > th { + background-color: rgba(0, 0, 0, .075) +} + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, .075) +} + +.table-hover .table-active:hover > td, .table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, .075) +} + +.table .thead-dark th { + color: #fff; + background-color: #343a40; + border-color: #454d55 +} + +.table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #dee2e6 +} + +.table-dark { + color: #fff; + background-color: #343a40 +} + +.table-dark td, .table-dark th, .table-dark thead th { + border-color: #454d55 +} + +.table-dark.table-bordered { + border: 0 +} + +.table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, .05) +} + +.table-dark.table-hover tbody tr:hover { + color: #fff; + background-color: rgba(255, 255, 255, .075) +} + +@media (max-width: 575.98px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch + } + + .table-responsive-sm > .table-bordered { + border: 0 + } +} + +@media (max-width: 767.98px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch + } + + .table-responsive-md > .table-bordered { + border: 0 + } +} + +@media (max-width: 991.98px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch + } + + .table-responsive-lg > .table-bordered { + border: 0 + } +} + +@media (max-width: 1199.98px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch + } + + .table-responsive-xl > .table-bordered { + border: 0 + } +} + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch +} + +.table-responsive > .table-bordered { + border: 0 +} + +.form-control { + display: block; + width: 100%; + height: calc(1.5em + .75rem + 2px); + padding: .375rem .75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: .25rem; + transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion: reduce) { + .form-control { + transition: none + } +} + +.form-control::-ms-expand { + background-color: transparent; + border: 0 +} + +.form-control:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057 +} + +.form-control:focus { + color: #495057; + background-color: #fff; + border-color: #80bdff; + outline: 0; + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.form-control::-webkit-input-placeholder { + color: #6c757d; + opacity: 1 +} + +.form-control::-moz-placeholder { + color: #6c757d; + opacity: 1 +} + +.form-control:-ms-input-placeholder { + color: #6c757d; + opacity: 1 +} + +.form-control::-ms-input-placeholder { + color: #6c757d; + opacity: 1 +} + +.form-control::placeholder { + color: #6c757d; + opacity: 1 +} + +.form-control:disabled, .form-control[readonly] { + background-color: #e9ecef; + opacity: 1 +} + +input[type=date].form-control, input[type=datetime-local].form-control, input[type=month].form-control, input[type=time].form-control { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none +} + +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff +} + +.form-control-file, .form-control-range { + display: block; + width: 100% +} + +.col-form-label { + padding-top: calc(.375rem + 1px); + padding-bottom: calc(.375rem + 1px); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5 +} + +.col-form-label-lg { + padding-top: calc(.5rem + 1px); + padding-bottom: calc(.5rem + 1px); + font-size: 1.25rem; + line-height: 1.5 +} + +.col-form-label-sm { + padding-top: calc(.25rem + 1px); + padding-bottom: calc(.25rem + 1px); + font-size: .875rem; + line-height: 1.5 +} + +.form-control-plaintext { + display: block; + width: 100%; + padding: .375rem 0; + margin-bottom: 0; + font-size: 1rem; + line-height: 1.5; + color: #212529; + background-color: transparent; + border: solid transparent; + border-width: 1px 0 +} + +.form-control-plaintext.form-control-lg, .form-control-plaintext.form-control-sm { + padding-right: 0; + padding-left: 0 +} + +.form-control-sm { + height: calc(1.5em + .5rem + 2px); + padding: .25rem .5rem; + font-size: .875rem; + line-height: 1.5; + border-radius: .2rem +} + +.form-control-lg { + height: calc(1.5em + 1rem + 2px); + padding: .5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: .3rem +} + +select.form-control[multiple], select.form-control[size] { + height: auto +} + +textarea.form-control { + height: auto +} + +.form-group { + margin-bottom: 1rem +} + +.form-text { + display: block; + margin-top: .25rem +} + +.form-row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px +} + +.form-row > .col, .form-row > [class*=col-] { + padding-right: 5px; + padding-left: 5px +} + +.form-check { + position: relative; + display: block; + padding-left: 1.25rem +} + +.form-check-input { + position: absolute; + margin-top: .3rem; + margin-left: -1.25rem +} + +.form-check-input:disabled ~ .form-check-label, .form-check-input[disabled] ~ .form-check-label { + color: #6c757d +} + +.form-check-label { + margin-bottom: 0 +} + +.form-check-inline { + display: -ms-inline-flexbox; + display: inline-flex; + -ms-flex-align: center; + align-items: center; + padding-left: 0; + margin-right: .75rem +} + +.form-check-inline .form-check-input { + position: static; + margin-top: 0; + margin-right: .3125rem; + margin-left: 0 +} + +.valid-feedback { + display: none; + width: 100%; + margin-top: .25rem; + font-size: 80%; + color: #28a745 +} + +.valid-tooltip { + position: absolute; + top: 100%; + left: 0; + z-index: 5; + display: none; + max-width: 100%; + padding: .25rem .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(40, 167, 69, .9); + border-radius: .25rem +} + +.is-valid ~ .valid-feedback, .is-valid ~ .valid-tooltip, .was-validated :valid ~ .valid-feedback, .was-validated :valid ~ .valid-tooltip { + display: block +} + +.form-control.is-valid, .was-validated .form-control:valid { + border-color: #28a745; + padding-right: calc(1.5em + .75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(.375em + .1875rem) center; + background-size: calc(.75em + .375rem) calc(.75em + .375rem) +} + +.form-control.is-valid:focus, .was-validated .form-control:valid:focus { + border-color: #28a745; + box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .25) +} + +.was-validated textarea.form-control:valid, textarea.form-control.is-valid { + padding-right: calc(1.5em + .75rem); + background-position: top calc(.375em + .1875rem) right calc(.375em + .1875rem) +} + +.custom-select.is-valid, .was-validated .custom-select:valid { + border-color: #28a745; + padding-right: calc(.75em + 2.3125rem); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) +} + +.custom-select.is-valid:focus, .was-validated .custom-select:valid:focus { + border-color: #28a745; + box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .25) +} + +.form-check-input.is-valid ~ .form-check-label, .was-validated .form-check-input:valid ~ .form-check-label { + color: #28a745 +} + +.form-check-input.is-valid ~ .valid-feedback, .form-check-input.is-valid ~ .valid-tooltip, .was-validated .form-check-input:valid ~ .valid-feedback, .was-validated .form-check-input:valid ~ .valid-tooltip { + display: block +} + +.custom-control-input.is-valid ~ .custom-control-label, .was-validated .custom-control-input:valid ~ .custom-control-label { + color: #28a745 +} + +.custom-control-input.is-valid ~ .custom-control-label::before, .was-validated .custom-control-input:valid ~ .custom-control-label::before { + border-color: #28a745 +} + +.custom-control-input.is-valid:checked ~ .custom-control-label::before, .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before { + border-color: #34ce57; + background-color: #34ce57 +} + +.custom-control-input.is-valid:focus ~ .custom-control-label::before, .was-validated .custom-control-input:valid:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .25) +} + +.custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before, .was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before { + border-color: #28a745 +} + +.custom-file-input.is-valid ~ .custom-file-label, .was-validated .custom-file-input:valid ~ .custom-file-label { + border-color: #28a745 +} + +.custom-file-input.is-valid:focus ~ .custom-file-label, .was-validated .custom-file-input:valid:focus ~ .custom-file-label { + border-color: #28a745; + box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .25) +} + +.invalid-feedback { + display: none; + width: 100%; + margin-top: .25rem; + font-size: 80%; + color: #dc3545 +} + +.invalid-tooltip { + position: absolute; + top: 100%; + left: 0; + z-index: 5; + display: none; + max-width: 100%; + padding: .25rem .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(220, 53, 69, .9); + border-radius: .25rem +} + +.is-invalid ~ .invalid-feedback, .is-invalid ~ .invalid-tooltip, .was-validated :invalid ~ .invalid-feedback, .was-validated :invalid ~ .invalid-tooltip { + display: block +} + +.form-control.is-invalid, .was-validated .form-control:invalid { + border-color: #dc3545; + padding-right: calc(1.5em + .75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(.375em + .1875rem) center; + background-size: calc(.75em + .375rem) calc(.75em + .375rem) +} + +.form-control.is-invalid:focus, .was-validated .form-control:invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 .2rem rgba(220, 53, 69, .25) +} + +.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { + padding-right: calc(1.5em + .75rem); + background-position: top calc(.375em + .1875rem) right calc(.375em + .1875rem) +} + +.custom-select.is-invalid, .was-validated .custom-select:invalid { + border-color: #dc3545; + padding-right: calc(.75em + 2.3125rem); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) +} + +.custom-select.is-invalid:focus, .was-validated .custom-select:invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 .2rem rgba(220, 53, 69, .25) +} + +.form-check-input.is-invalid ~ .form-check-label, .was-validated .form-check-input:invalid ~ .form-check-label { + color: #dc3545 +} + +.form-check-input.is-invalid ~ .invalid-feedback, .form-check-input.is-invalid ~ .invalid-tooltip, .was-validated .form-check-input:invalid ~ .invalid-feedback, .was-validated .form-check-input:invalid ~ .invalid-tooltip { + display: block +} + +.custom-control-input.is-invalid ~ .custom-control-label, .was-validated .custom-control-input:invalid ~ .custom-control-label { + color: #dc3545 +} + +.custom-control-input.is-invalid ~ .custom-control-label::before, .was-validated .custom-control-input:invalid ~ .custom-control-label::before { + border-color: #dc3545 +} + +.custom-control-input.is-invalid:checked ~ .custom-control-label::before, .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before { + border-color: #e4606d; + background-color: #e4606d +} + +.custom-control-input.is-invalid:focus ~ .custom-control-label::before, .was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 .2rem rgba(220, 53, 69, .25) +} + +.custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before, .was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before { + border-color: #dc3545 +} + +.custom-file-input.is-invalid ~ .custom-file-label, .was-validated .custom-file-input:invalid ~ .custom-file-label { + border-color: #dc3545 +} + +.custom-file-input.is-invalid:focus ~ .custom-file-label, .was-validated .custom-file-input:invalid:focus ~ .custom-file-label { + border-color: #dc3545; + box-shadow: 0 0 0 .2rem rgba(220, 53, 69, .25) +} + +.form-inline { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -ms-flex-align: center; + align-items: center +} + +.form-inline .form-check { + width: 100% +} + +@media (min-width: 576px) { + .form-inline label { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + margin-bottom: 0 + } + + .form-inline .form-group { + display: -ms-flexbox; + display: flex; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -ms-flex-align: center; + align-items: center; + margin-bottom: 0 + } + + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle + } + + .form-inline .form-control-plaintext { + display: inline-block + } + + .form-inline .custom-select, .form-inline .input-group { + width: auto + } + + .form-inline .form-check { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: auto; + padding-left: 0 + } + + .form-inline .form-check-input { + position: relative; + -ms-flex-negative: 0; + flex-shrink: 0; + margin-top: 0; + margin-right: .25rem; + margin-left: 0 + } + + .form-inline .custom-control { + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center + } + + .form-inline .custom-control-label { + margin-bottom: 0 + } +} + +.btn { + display: inline-block; + font-weight: 400; + color: #212529; + text-align: center; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: .375rem .75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: .25rem; + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion: reduce) { + .btn { + transition: none + } +} + +.btn:hover { + color: #212529; + text-decoration: none +} + +.btn.focus, .btn:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.btn.disabled, .btn:disabled { + opacity: .65 +} + +.btn:not(:disabled):not(.disabled) { + cursor: pointer +} + +a.btn.disabled, fieldset:disabled a.btn { + pointer-events: none +} + +.btn-primary { + color: #fff; + background-color: #007bff; + border-color: #007bff +} + +.btn-primary:hover { + color: #fff; + background-color: #0069d9; + border-color: #0062cc +} + +.btn-primary.focus, .btn-primary:focus { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; + box-shadow: 0 0 0 .2rem rgba(38, 143, 255, .5) +} + +.btn-primary.disabled, .btn-primary:disabled { + color: #fff; + background-color: #007bff; + border-color: #007bff +} + +.btn-primary:not(:disabled):not(.disabled).active, .btn-primary:not(:disabled):not(.disabled):active, .show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #0062cc; + border-color: #005cbf +} + +.btn-primary:not(:disabled):not(.disabled).active:focus, .btn-primary:not(:disabled):not(.disabled):active:focus, .show > .btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(38, 143, 255, .5) +} + +.btn-secondary { + color: #fff; + background-color: #6c757d; + border-color: #6c757d +} + +.btn-secondary:hover { + color: #fff; + background-color: #5a6268; + border-color: #545b62 +} + +.btn-secondary.focus, .btn-secondary:focus { + color: #fff; + background-color: #5a6268; + border-color: #545b62; + box-shadow: 0 0 0 .2rem rgba(130, 138, 145, .5) +} + +.btn-secondary.disabled, .btn-secondary:disabled { + color: #fff; + background-color: #6c757d; + border-color: #6c757d +} + +.btn-secondary:not(:disabled):not(.disabled).active, .btn-secondary:not(:disabled):not(.disabled):active, .show > .btn-secondary.dropdown-toggle { + color: #fff; + background-color: #545b62; + border-color: #4e555b +} + +.btn-secondary:not(:disabled):not(.disabled).active:focus, .btn-secondary:not(:disabled):not(.disabled):active:focus, .show > .btn-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(130, 138, 145, .5) +} + +.btn-success { + color: #fff; + background-color: #28a745; + border-color: #28a745 +} + +.btn-success:hover { + color: #fff; + background-color: #218838; + border-color: #1e7e34 +} + +.btn-success.focus, .btn-success:focus { + color: #fff; + background-color: #218838; + border-color: #1e7e34; + box-shadow: 0 0 0 .2rem rgba(72, 180, 97, .5) +} + +.btn-success.disabled, .btn-success:disabled { + color: #fff; + background-color: #28a745; + border-color: #28a745 +} + +.btn-success:not(:disabled):not(.disabled).active, .btn-success:not(:disabled):not(.disabled):active, .show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #1e7e34; + border-color: #1c7430 +} + +.btn-success:not(:disabled):not(.disabled).active:focus, .btn-success:not(:disabled):not(.disabled):active:focus, .show > .btn-success.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(72, 180, 97, .5) +} + +.btn-info { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8 +} + +.btn-info:hover { + color: #fff; + background-color: #138496; + border-color: #117a8b +} + +.btn-info.focus, .btn-info:focus { + color: #fff; + background-color: #138496; + border-color: #117a8b; + box-shadow: 0 0 0 .2rem rgba(58, 176, 195, .5) +} + +.btn-info.disabled, .btn-info:disabled { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8 +} + +.btn-info:not(:disabled):not(.disabled).active, .btn-info:not(:disabled):not(.disabled):active, .show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #117a8b; + border-color: #10707f +} + +.btn-info:not(:disabled):not(.disabled).active:focus, .btn-info:not(:disabled):not(.disabled):active:focus, .show > .btn-info.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(58, 176, 195, .5) +} + +.btn-warning { + color: #212529; + background-color: #ffc107; + border-color: #ffc107 +} + +.btn-warning:hover { + color: #212529; + background-color: #e0a800; + border-color: #d39e00 +} + +.btn-warning.focus, .btn-warning:focus { + color: #212529; + background-color: #e0a800; + border-color: #d39e00; + box-shadow: 0 0 0 .2rem rgba(222, 170, 12, .5) +} + +.btn-warning.disabled, .btn-warning:disabled { + color: #212529; + background-color: #ffc107; + border-color: #ffc107 +} + +.btn-warning:not(:disabled):not(.disabled).active, .btn-warning:not(:disabled):not(.disabled):active, .show > .btn-warning.dropdown-toggle { + color: #212529; + background-color: #d39e00; + border-color: #c69500 +} + +.btn-warning:not(:disabled):not(.disabled).active:focus, .btn-warning:not(:disabled):not(.disabled):active:focus, .show > .btn-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(222, 170, 12, .5) +} + +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545 +} + +.btn-danger:hover { + color: #fff; + background-color: #c82333; + border-color: #bd2130 +} + +.btn-danger.focus, .btn-danger:focus { + color: #fff; + background-color: #c82333; + border-color: #bd2130; + box-shadow: 0 0 0 .2rem rgba(225, 83, 97, .5) +} + +.btn-danger.disabled, .btn-danger:disabled { + color: #fff; + background-color: #dc3545; + border-color: #dc3545 +} + +.btn-danger:not(:disabled):not(.disabled).active, .btn-danger:not(:disabled):not(.disabled):active, .show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #bd2130; + border-color: #b21f2d +} + +.btn-danger:not(:disabled):not(.disabled).active:focus, .btn-danger:not(:disabled):not(.disabled):active:focus, .show > .btn-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(225, 83, 97, .5) +} + +.btn-light { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa +} + +.btn-light:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5 +} + +.btn-light.focus, .btn-light:focus { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; + box-shadow: 0 0 0 .2rem rgba(216, 217, 219, .5) +} + +.btn-light.disabled, .btn-light:disabled { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa +} + +.btn-light:not(:disabled):not(.disabled).active, .btn-light:not(:disabled):not(.disabled):active, .show > .btn-light.dropdown-toggle { + color: #212529; + background-color: #dae0e5; + border-color: #d3d9df +} + +.btn-light:not(:disabled):not(.disabled).active:focus, .btn-light:not(:disabled):not(.disabled):active:focus, .show > .btn-light.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(216, 217, 219, .5) +} + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40 +} + +.btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124 +} + +.btn-dark.focus, .btn-dark:focus { + color: #fff; + background-color: #23272b; + border-color: #1d2124; + box-shadow: 0 0 0 .2rem rgba(82, 88, 93, .5) +} + +.btn-dark.disabled, .btn-dark:disabled { + color: #fff; + background-color: #343a40; + border-color: #343a40 +} + +.btn-dark:not(:disabled):not(.disabled).active, .btn-dark:not(:disabled):not(.disabled):active, .show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1d2124; + border-color: #171a1d +} + +.btn-dark:not(:disabled):not(.disabled).active:focus, .btn-dark:not(:disabled):not(.disabled):active:focus, .show > .btn-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(82, 88, 93, .5) +} + +.btn-outline-primary { + color: #007bff; + border-color: #007bff +} + +.btn-outline-primary:hover { + color: #fff; + background-color: #007bff; + border-color: #007bff +} + +.btn-outline-primary.focus, .btn-outline-primary:focus { + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .5) +} + +.btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #007bff; + background-color: transparent +} + +.btn-outline-primary:not(:disabled):not(.disabled).active, .btn-outline-primary:not(:disabled):not(.disabled):active, .show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #007bff; + border-color: #007bff +} + +.btn-outline-primary:not(:disabled):not(.disabled).active:focus, .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .5) +} + +.btn-outline-secondary { + color: #6c757d; + border-color: #6c757d +} + +.btn-outline-secondary:hover { + color: #fff; + background-color: #6c757d; + border-color: #6c757d +} + +.btn-outline-secondary.focus, .btn-outline-secondary:focus { + box-shadow: 0 0 0 .2rem rgba(108, 117, 125, .5) +} + +.btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #6c757d; + background-color: transparent +} + +.btn-outline-secondary:not(:disabled):not(.disabled).active, .btn-outline-secondary:not(:disabled):not(.disabled):active, .show > .btn-outline-secondary.dropdown-toggle { + color: #fff; + background-color: #6c757d; + border-color: #6c757d +} + +.btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(108, 117, 125, .5) +} + +.btn-outline-success { + color: #28a745; + border-color: #28a745 +} + +.btn-outline-success:hover { + color: #fff; + background-color: #28a745; + border-color: #28a745 +} + +.btn-outline-success.focus, .btn-outline-success:focus { + box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .5) +} + +.btn-outline-success.disabled, .btn-outline-success:disabled { + color: #28a745; + background-color: transparent +} + +.btn-outline-success:not(:disabled):not(.disabled).active, .btn-outline-success:not(:disabled):not(.disabled):active, .show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: #28a745; + border-color: #28a745 +} + +.btn-outline-success:not(:disabled):not(.disabled).active:focus, .btn-outline-success:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-success.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .5) +} + +.btn-outline-info { + color: #17a2b8; + border-color: #17a2b8 +} + +.btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8 +} + +.btn-outline-info.focus, .btn-outline-info:focus { + box-shadow: 0 0 0 .2rem rgba(23, 162, 184, .5) +} + +.btn-outline-info.disabled, .btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent +} + +.btn-outline-info:not(:disabled):not(.disabled).active, .btn-outline-info:not(:disabled):not(.disabled):active, .show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8 +} + +.btn-outline-info:not(:disabled):not(.disabled).active:focus, .btn-outline-info:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-info.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(23, 162, 184, .5) +} + +.btn-outline-warning { + color: #ffc107; + border-color: #ffc107 +} + +.btn-outline-warning:hover { + color: #212529; + background-color: #ffc107; + border-color: #ffc107 +} + +.btn-outline-warning.focus, .btn-outline-warning:focus { + box-shadow: 0 0 0 .2rem rgba(255, 193, 7, .5) +} + +.btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #ffc107; + background-color: transparent +} + +.btn-outline-warning:not(:disabled):not(.disabled).active, .btn-outline-warning:not(:disabled):not(.disabled):active, .show > .btn-outline-warning.dropdown-toggle { + color: #212529; + background-color: #ffc107; + border-color: #ffc107 +} + +.btn-outline-warning:not(:disabled):not(.disabled).active:focus, .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(255, 193, 7, .5) +} + +.btn-outline-danger { + color: #dc3545; + border-color: #dc3545 +} + +.btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545 +} + +.btn-outline-danger.focus, .btn-outline-danger:focus { + box-shadow: 0 0 0 .2rem rgba(220, 53, 69, .5) +} + +.btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #dc3545; + background-color: transparent +} + +.btn-outline-danger:not(:disabled):not(.disabled).active, .btn-outline-danger:not(:disabled):not(.disabled):active, .show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #dc3545; + border-color: #dc3545 +} + +.btn-outline-danger:not(:disabled):not(.disabled).active:focus, .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(220, 53, 69, .5) +} + +.btn-outline-light { + color: #f8f9fa; + border-color: #f8f9fa +} + +.btn-outline-light:hover { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa +} + +.btn-outline-light.focus, .btn-outline-light:focus { + box-shadow: 0 0 0 .2rem rgba(248, 249, 250, .5) +} + +.btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent +} + +.btn-outline-light:not(:disabled):not(.disabled).active, .btn-outline-light:not(:disabled):not(.disabled):active, .show > .btn-outline-light.dropdown-toggle { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa +} + +.btn-outline-light:not(:disabled):not(.disabled).active:focus, .btn-outline-light:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-light.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(248, 249, 250, .5) +} + +.btn-outline-dark { + color: #343a40; + border-color: #343a40 +} + +.btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40 +} + +.btn-outline-dark.focus, .btn-outline-dark:focus { + box-shadow: 0 0 0 .2rem rgba(52, 58, 64, .5) +} + +.btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent +} + +.btn-outline-dark:not(:disabled):not(.disabled).active, .btn-outline-dark:not(:disabled):not(.disabled):active, .show > .btn-outline-dark.dropdown-toggle { + color: #fff; + background-color: #343a40; + border-color: #343a40 +} + +.btn-outline-dark:not(:disabled):not(.disabled).active:focus, .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .show > .btn-outline-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 .2rem rgba(52, 58, 64, .5) +} + +.btn-link { + font-weight: 400; + color: #007bff; + text-decoration: none +} + +.btn-link:hover { + color: #0056b3; + text-decoration: underline +} + +.btn-link.focus, .btn-link:focus { + text-decoration: underline +} + +.btn-link.disabled, .btn-link:disabled { + color: #6c757d; + pointer-events: none +} + +.btn-group-lg > .btn, .btn-lg { + padding: .5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: .3rem +} + +.btn-group-sm > .btn, .btn-sm { + padding: .25rem .5rem; + font-size: .875rem; + line-height: 1.5; + border-radius: .2rem +} + +.btn-block { + display: block; + width: 100% +} + +.btn-block + .btn-block { + margin-top: .5rem +} + +input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].btn-block { + width: 100% +} + +.fade { + transition: opacity .15s linear +} + +@media (prefers-reduced-motion: reduce) { + .fade { + transition: none + } +} + +.fade:not(.show) { + opacity: 0 +} + +.collapse:not(.show) { + display: none +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + transition: height .35s ease +} + +@media (prefers-reduced-motion: reduce) { + .collapsing { + transition: none + } +} + +.dropdown, .dropleft, .dropright, .dropup { + position: relative +} + +.dropdown-toggle { + white-space: nowrap +} + +.dropdown-toggle::after { + display: inline-block; + margin-left: .255em; + vertical-align: .255em; + content: ""; + border-top: .3em solid; + border-right: .3em solid transparent; + border-bottom: 0; + border-left: .3em solid transparent +} + +.dropdown-toggle:empty::after { + margin-left: 0 +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: .5rem 0; + margin: .125rem 0 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: .25rem +} + +.dropdown-menu-left { + right: auto; + left: 0 +} + +.dropdown-menu-right { + right: 0; + left: auto +} + +@media (min-width: 576px) { + .dropdown-menu-sm-left { + right: auto; + left: 0 + } + + .dropdown-menu-sm-right { + right: 0; + left: auto + } +} + +@media (min-width: 768px) { + .dropdown-menu-md-left { + right: auto; + left: 0 + } + + .dropdown-menu-md-right { + right: 0; + left: auto + } +} + +@media (min-width: 992px) { + .dropdown-menu-lg-left { + right: auto; + left: 0 + } + + .dropdown-menu-lg-right { + right: 0; + left: auto + } +} + +@media (min-width: 1200px) { + .dropdown-menu-xl-left { + right: auto; + left: 0 + } + + .dropdown-menu-xl-right { + right: 0; + left: auto + } +} + +.dropup .dropdown-menu { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: .125rem +} + +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: .255em; + vertical-align: .255em; + content: ""; + border-top: 0; + border-right: .3em solid transparent; + border-bottom: .3em solid; + border-left: .3em solid transparent +} + +.dropup .dropdown-toggle:empty::after { + margin-left: 0 +} + +.dropright .dropdown-menu { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: .125rem +} + +.dropright .dropdown-toggle::after { + display: inline-block; + margin-left: .255em; + vertical-align: .255em; + content: ""; + border-top: .3em solid transparent; + border-right: 0; + border-bottom: .3em solid transparent; + border-left: .3em solid +} + +.dropright .dropdown-toggle:empty::after { + margin-left: 0 +} + +.dropright .dropdown-toggle::after { + vertical-align: 0 +} + +.dropleft .dropdown-menu { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: .125rem +} + +.dropleft .dropdown-toggle::after { + display: inline-block; + margin-left: .255em; + vertical-align: .255em; + content: "" +} + +.dropleft .dropdown-toggle::after { + display: none +} + +.dropleft .dropdown-toggle::before { + display: inline-block; + margin-right: .255em; + vertical-align: .255em; + content: ""; + border-top: .3em solid transparent; + border-right: .3em solid; + border-bottom: .3em solid transparent +} + +.dropleft .dropdown-toggle:empty::after { + margin-left: 0 +} + +.dropleft .dropdown-toggle::before { + vertical-align: 0 +} + +.dropdown-menu[x-placement^=bottom], .dropdown-menu[x-placement^=left], .dropdown-menu[x-placement^=right], .dropdown-menu[x-placement^=top] { + right: auto; + bottom: auto +} + +.dropdown-divider { + height: 0; + margin: .5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef +} + +.dropdown-item { + display: block; + width: 100%; + padding: .25rem 1.5rem; + clear: both; + font-weight: 400; + color: #212529; + text-align: inherit; + white-space: nowrap; + background-color: transparent; + border: 0 +} + +.dropdown-item:focus, .dropdown-item:hover { + color: #16181b; + text-decoration: none; + background-color: #f8f9fa +} + +.dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #007bff +} + +.dropdown-item.disabled, .dropdown-item:disabled { + color: #6c757d; + pointer-events: none; + background-color: transparent +} + +.dropdown-menu.show { + display: block +} + +.dropdown-header { + display: block; + padding: .5rem 1.5rem; + margin-bottom: 0; + font-size: .875rem; + color: #6c757d; + white-space: nowrap +} + +.dropdown-item-text { + display: block; + padding: .25rem 1.5rem; + color: #212529 +} + +.btn-group, .btn-group-vertical { + position: relative; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: middle +} + +.btn-group-vertical > .btn, .btn-group > .btn { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto +} + +.btn-group-vertical > .btn:hover, .btn-group > .btn:hover { + z-index: 1 +} + +.btn-group-vertical > .btn.active, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn:focus, .btn-group > .btn.active, .btn-group > .btn:active, .btn-group > .btn:focus { + z-index: 1 +} + +.btn-toolbar { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-pack: start; + justify-content: flex-start +} + +.btn-toolbar .input-group { + width: auto +} + +.btn-group > .btn-group:not(:first-child), .btn-group > .btn:not(:first-child) { + margin-left: -1px +} + +.btn-group > .btn-group:not(:last-child) > .btn, .btn-group > .btn:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.btn-group > .btn-group:not(:first-child) > .btn, .btn-group > .btn:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.dropdown-toggle-split { + padding-right: .5625rem; + padding-left: .5625rem +} + +.dropdown-toggle-split::after, .dropright .dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after { + margin-left: 0 +} + +.dropleft .dropdown-toggle-split::before { + margin-right: 0 +} + +.btn-group-sm > .btn + .dropdown-toggle-split, .btn-sm + .dropdown-toggle-split { + padding-right: .375rem; + padding-left: .375rem +} + +.btn-group-lg > .btn + .dropdown-toggle-split, .btn-lg + .dropdown-toggle-split { + padding-right: .75rem; + padding-left: .75rem +} + +.btn-group-vertical { + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-align: start; + align-items: flex-start; + -ms-flex-pack: center; + justify-content: center +} + +.btn-group-vertical > .btn, .btn-group-vertical > .btn-group { + width: 100% +} + +.btn-group-vertical > .btn-group:not(:first-child), .btn-group-vertical > .btn:not(:first-child) { + margin-top: -1px +} + +.btn-group-vertical > .btn-group:not(:last-child) > .btn, .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0 +} + +.btn-group-vertical > .btn-group:not(:first-child) > .btn, .btn-group-vertical > .btn:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.btn-group-toggle > .btn, .btn-group-toggle > .btn-group > .btn { + margin-bottom: 0 +} + +.btn-group-toggle > .btn input[type=checkbox], .btn-group-toggle > .btn input[type=radio], .btn-group-toggle > .btn-group > .btn input[type=checkbox], .btn-group-toggle > .btn-group > .btn input[type=radio] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none +} + +.input-group { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: stretch; + align-items: stretch; + width: 100% +} + +.input-group > .custom-file, .input-group > .custom-select, .input-group > .form-control, .input-group > .form-control-plaintext { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + width: 1%; + min-width: 0; + margin-bottom: 0 +} + +.input-group > .custom-file + .custom-file, .input-group > .custom-file + .custom-select, .input-group > .custom-file + .form-control, .input-group > .custom-select + .custom-file, .input-group > .custom-select + .custom-select, .input-group > .custom-select + .form-control, .input-group > .form-control + .custom-file, .input-group > .form-control + .custom-select, .input-group > .form-control + .form-control, .input-group > .form-control-plaintext + .custom-file, .input-group > .form-control-plaintext + .custom-select, .input-group > .form-control-plaintext + .form-control { + margin-left: -1px +} + +.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label, .input-group > .custom-select:focus, .input-group > .form-control:focus { + z-index: 3 +} + +.input-group > .custom-file .custom-file-input:focus { + z-index: 4 +} + +.input-group > .custom-select:not(:last-child), .input-group > .form-control:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.input-group > .custom-select:not(:first-child), .input-group > .form-control:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.input-group > .custom-file { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center +} + +.input-group > .custom-file:not(:last-child) .custom-file-label, .input-group > .custom-file:not(:last-child) .custom-file-label::after { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.input-group > .custom-file:not(:first-child) .custom-file-label { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.input-group-append, .input-group-prepend { + display: -ms-flexbox; + display: flex +} + +.input-group-append .btn, .input-group-prepend .btn { + position: relative; + z-index: 2 +} + +.input-group-append .btn:focus, .input-group-prepend .btn:focus { + z-index: 3 +} + +.input-group-append .btn + .btn, .input-group-append .btn + .input-group-text, .input-group-append .input-group-text + .btn, .input-group-append .input-group-text + .input-group-text, .input-group-prepend .btn + .btn, .input-group-prepend .btn + .input-group-text, .input-group-prepend .input-group-text + .btn, .input-group-prepend .input-group-text + .input-group-text { + margin-left: -1px +} + +.input-group-prepend { + margin-right: -1px +} + +.input-group-append { + margin-left: -1px +} + +.input-group-text { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding: .375rem .75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + white-space: nowrap; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: .25rem +} + +.input-group-text input[type=checkbox], .input-group-text input[type=radio] { + margin-top: 0 +} + +.input-group-lg > .custom-select, .input-group-lg > .form-control:not(textarea) { + height: calc(1.5em + 1rem + 2px) +} + +.input-group-lg > .custom-select, .input-group-lg > .form-control, .input-group-lg > .input-group-append > .btn, .input-group-lg > .input-group-append > .input-group-text, .input-group-lg > .input-group-prepend > .btn, .input-group-lg > .input-group-prepend > .input-group-text { + padding: .5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: .3rem +} + +.input-group-sm > .custom-select, .input-group-sm > .form-control:not(textarea) { + height: calc(1.5em + .5rem + 2px) +} + +.input-group-sm > .custom-select, .input-group-sm > .form-control, .input-group-sm > .input-group-append > .btn, .input-group-sm > .input-group-append > .input-group-text, .input-group-sm > .input-group-prepend > .btn, .input-group-sm > .input-group-prepend > .input-group-text { + padding: .25rem .5rem; + font-size: .875rem; + line-height: 1.5; + border-radius: .2rem +} + +.input-group-lg > .custom-select, .input-group-sm > .custom-select { + padding-right: 1.75rem +} + +.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group > .input-group-append:last-child > .input-group-text:not(:last-child), .input-group > .input-group-append:not(:last-child) > .btn, .input-group > .input-group-append:not(:last-child) > .input-group-text, .input-group > .input-group-prepend > .btn, .input-group > .input-group-prepend > .input-group-text { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.input-group > .input-group-append > .btn, .input-group > .input-group-append > .input-group-text, .input-group > .input-group-prepend:first-child > .btn:not(:first-child), .input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child), .input-group > .input-group-prepend:not(:first-child) > .btn, .input-group > .input-group-prepend:not(:first-child) > .input-group-text { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.custom-control { + position: relative; + z-index: 1; + display: block; + min-height: 1.5rem; + padding-left: 1.5rem +} + +.custom-control-inline { + display: -ms-inline-flexbox; + display: inline-flex; + margin-right: 1rem +} + +.custom-control-input { + position: absolute; + left: 0; + z-index: -1; + width: 1rem; + height: 1.25rem; + opacity: 0 +} + +.custom-control-input:checked ~ .custom-control-label::before { + color: #fff; + border-color: #007bff; + background-color: #007bff +} + +.custom-control-input:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.custom-control-input:focus:not(:checked) ~ .custom-control-label::before { + border-color: #80bdff +} + +.custom-control-input:not(:disabled):active ~ .custom-control-label::before { + color: #fff; + background-color: #b3d7ff; + border-color: #b3d7ff +} + +.custom-control-input:disabled ~ .custom-control-label, .custom-control-input[disabled] ~ .custom-control-label { + color: #6c757d +} + +.custom-control-input:disabled ~ .custom-control-label::before, .custom-control-input[disabled] ~ .custom-control-label::before { + background-color: #e9ecef +} + +.custom-control-label { + position: relative; + margin-bottom: 0; + vertical-align: top +} + +.custom-control-label::before { + position: absolute; + top: .25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + content: ""; + background-color: #fff; + border: #adb5bd solid 1px +} + +.custom-control-label::after { + position: absolute; + top: .25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + content: ""; + background: no-repeat 50%/50% 50% +} + +.custom-checkbox .custom-control-label::before { + border-radius: .25rem +} + +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e") +} + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { + border-color: #007bff; + background-color: #007bff +} + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e") +} + +.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, .5) +} + +.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, .5) +} + +.custom-radio .custom-control-label::before { + border-radius: 50% +} + +.custom-radio .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e") +} + +.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, .5) +} + +.custom-switch { + padding-left: 2.25rem +} + +.custom-switch .custom-control-label::before { + left: -2.25rem; + width: 1.75rem; + pointer-events: all; + border-radius: .5rem +} + +.custom-switch .custom-control-label::after { + top: calc(.25rem + 2px); + left: calc(-2.25rem + 2px); + width: calc(1rem - 4px); + height: calc(1rem - 4px); + background-color: #adb5bd; + border-radius: .5rem; + transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out, -webkit-transform .15s ease-in-out; + transition: transform .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + transition: transform .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out, -webkit-transform .15s ease-in-out +} + +@media (prefers-reduced-motion: reduce) { + .custom-switch .custom-control-label::after { + transition: none + } +} + +.custom-switch .custom-control-input:checked ~ .custom-control-label::after { + background-color: #fff; + -webkit-transform: translateX(.75rem); + transform: translateX(.75rem) +} + +.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, .5) +} + +.custom-select { + display: inline-block; + width: 100%; + height: calc(1.5em + .75rem + 2px); + padding: .375rem 1.75rem .375rem .75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px; + border: 1px solid #ced4da; + border-radius: .25rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none +} + +.custom-select:focus { + border-color: #80bdff; + outline: 0; + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.custom-select:focus::-ms-value { + color: #495057; + background-color: #fff +} + +.custom-select[multiple], .custom-select[size]:not([size="1"]) { + height: auto; + padding-right: .75rem; + background-image: none +} + +.custom-select:disabled { + color: #6c757d; + background-color: #e9ecef +} + +.custom-select::-ms-expand { + display: none +} + +.custom-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057 +} + +.custom-select-sm { + height: calc(1.5em + .5rem + 2px); + padding-top: .25rem; + padding-bottom: .25rem; + padding-left: .5rem; + font-size: .875rem +} + +.custom-select-lg { + height: calc(1.5em + 1rem + 2px); + padding-top: .5rem; + padding-bottom: .5rem; + padding-left: 1rem; + font-size: 1.25rem +} + +.custom-file { + position: relative; + display: inline-block; + width: 100%; + height: calc(1.5em + .75rem + 2px); + margin-bottom: 0 +} + +.custom-file-input { + position: relative; + z-index: 2; + width: 100%; + height: calc(1.5em + .75rem + 2px); + margin: 0; + opacity: 0 +} + +.custom-file-input:focus ~ .custom-file-label { + border-color: #80bdff; + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.custom-file-input:disabled ~ .custom-file-label, .custom-file-input[disabled] ~ .custom-file-label { + background-color: #e9ecef +} + +.custom-file-input:lang(en) ~ .custom-file-label::after { + content: "Browse" +} + +.custom-file-input ~ .custom-file-label[data-browse]::after { + content: attr(data-browse) +} + +.custom-file-label { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + height: calc(1.5em + .75rem + 2px); + padding: .375rem .75rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: .25rem +} + +.custom-file-label::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 3; + display: block; + height: calc(1.5em + .75rem); + padding: .375rem .75rem; + line-height: 1.5; + color: #495057; + content: "Browse"; + background-color: #e9ecef; + border-left: inherit; + border-radius: 0 .25rem .25rem 0 +} + +.custom-range { + width: 100%; + height: 1.4rem; + padding: 0; + background-color: transparent; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none +} + +.custom-range:focus { + outline: 0 +} + +.custom-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.custom-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.custom-range:focus::-ms-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.custom-range::-moz-focus-outer { + border: 0 +} + +.custom-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -.25rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -webkit-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + -webkit-appearance: none; + appearance: none +} + +@media (prefers-reduced-motion: reduce) { + .custom-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none + } +} + +.custom-range::-webkit-slider-thumb:active { + background-color: #b3d7ff +} + +.custom-range::-webkit-slider-runnable-track { + width: 100%; + height: .5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem +} + +.custom-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -moz-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + -moz-appearance: none; + appearance: none +} + +@media (prefers-reduced-motion: reduce) { + .custom-range::-moz-range-thumb { + -moz-transition: none; + transition: none + } +} + +.custom-range::-moz-range-thumb:active { + background-color: #b3d7ff +} + +.custom-range::-moz-range-track { + width: 100%; + height: .5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem +} + +.custom-range::-ms-thumb { + width: 1rem; + height: 1rem; + margin-top: 0; + margin-right: .2rem; + margin-left: .2rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -ms-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; + appearance: none +} + +@media (prefers-reduced-motion: reduce) { + .custom-range::-ms-thumb { + -ms-transition: none; + transition: none + } +} + +.custom-range::-ms-thumb:active { + background-color: #b3d7ff +} + +.custom-range::-ms-track { + width: 100%; + height: .5rem; + color: transparent; + cursor: pointer; + background-color: transparent; + border-color: transparent; + border-width: .5rem +} + +.custom-range::-ms-fill-lower { + background-color: #dee2e6; + border-radius: 1rem +} + +.custom-range::-ms-fill-upper { + margin-right: 15px; + background-color: #dee2e6; + border-radius: 1rem +} + +.custom-range:disabled::-webkit-slider-thumb { + background-color: #adb5bd +} + +.custom-range:disabled::-webkit-slider-runnable-track { + cursor: default +} + +.custom-range:disabled::-moz-range-thumb { + background-color: #adb5bd +} + +.custom-range:disabled::-moz-range-track { + cursor: default +} + +.custom-range:disabled::-ms-thumb { + background-color: #adb5bd +} + +.custom-control-label::before, .custom-file-label, .custom-select { + transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion: reduce) { + .custom-control-label::before, .custom-file-label, .custom-select { + transition: none + } +} + +.nav { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none +} + +.nav-link { + display: block; + padding: .5rem 1rem +} + +.nav-link:focus, .nav-link:hover { + text-decoration: none +} + +.nav-link.disabled { + color: #6c757d; + pointer-events: none; + cursor: default +} + +.nav-tabs { + border-bottom: 1px solid #dee2e6 +} + +.nav-tabs .nav-item { + margin-bottom: -1px +} + +.nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: .25rem; + border-top-right-radius: .25rem +} + +.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { + border-color: #e9ecef #e9ecef #dee2e6 +} + +.nav-tabs .nav-link.disabled { + color: #6c757d; + background-color: transparent; + border-color: transparent +} + +.nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active { + color: #495057; + background-color: #fff; + border-color: #dee2e6 #dee2e6 #fff +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.nav-pills .nav-link { + border-radius: .25rem +} + +.nav-pills .nav-link.active, .nav-pills .show > .nav-link { + color: #fff; + background-color: #007bff +} + +.nav-fill .nav-item, .nav-fill > .nav-link { + -ms-flex: 1 1 auto; + flex: 1 1 auto; + text-align: center +} + +.nav-justified .nav-item, .nav-justified > .nav-link { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + text-align: center +} + +.tab-content > .tab-pane { + display: none +} + +.tab-content > .active { + display: block +} + +.navbar { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: justify; + justify-content: space-between; + padding: .5rem 1rem +} + +.navbar .container, .navbar .container-fluid, .navbar .container-lg, .navbar .container-md, .navbar .container-sm, .navbar .container-xl { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: justify; + justify-content: space-between +} + +.navbar-brand { + display: inline-block; + padding-top: .3125rem; + padding-bottom: .3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap +} + +.navbar-brand:focus, .navbar-brand:hover { + text-decoration: none +} + +.navbar-nav { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none +} + +.navbar-nav .nav-link { + padding-right: 0; + padding-left: 0 +} + +.navbar-nav .dropdown-menu { + position: static; + float: none +} + +.navbar-text { + display: inline-block; + padding-top: .5rem; + padding-bottom: .5rem +} + +.navbar-collapse { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-align: center; + align-items: center +} + +.navbar-toggler { + padding: .25rem .75rem; + font-size: 1.25rem; + line-height: 1; + background-color: transparent; + border: 1px solid transparent; + border-radius: .25rem +} + +.navbar-toggler:focus, .navbar-toggler:hover { + text-decoration: none +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100% +} + +@media (max-width: 575.98px) { + .navbar-expand-sm > .container, .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-xl { + padding-right: 0; + padding-left: 0 + } +} + +@media (min-width: 576px) { + .navbar-expand-sm { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start + } + + .navbar-expand-sm .navbar-nav { + -ms-flex-direction: row; + flex-direction: row + } + + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem + } + + .navbar-expand-sm > .container, .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap + } + + .navbar-expand-sm .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto + } + + .navbar-expand-sm .navbar-toggler { + display: none + } +} + +@media (max-width: 767.98px) { + .navbar-expand-md > .container, .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-md, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-xl { + padding-right: 0; + padding-left: 0 + } +} + +@media (min-width: 768px) { + .navbar-expand-md { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start + } + + .navbar-expand-md .navbar-nav { + -ms-flex-direction: row; + flex-direction: row + } + + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-md .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem + } + + .navbar-expand-md > .container, .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-md, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap + } + + .navbar-expand-md .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto + } + + .navbar-expand-md .navbar-toggler { + display: none + } +} + +@media (max-width: 991.98px) { + .navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-xl { + padding-right: 0; + padding-left: 0 + } +} + +@media (min-width: 992px) { + .navbar-expand-lg { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start + } + + .navbar-expand-lg .navbar-nav { + -ms-flex-direction: row; + flex-direction: row + } + + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem + } + + .navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap + } + + .navbar-expand-lg .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto + } + + .navbar-expand-lg .navbar-toggler { + display: none + } +} + +@media (max-width: 1199.98px) { + .navbar-expand-xl > .container, .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-xl { + padding-right: 0; + padding-left: 0 + } +} + +@media (min-width: 1200px) { + .navbar-expand-xl { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start + } + + .navbar-expand-xl .navbar-nav { + -ms-flex-direction: row; + flex-direction: row + } + + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute + } + + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem + } + + .navbar-expand-xl > .container, .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap + } + + .navbar-expand-xl .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto + } + + .navbar-expand-xl .navbar-toggler { + display: none + } +} + +.navbar-expand { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start +} + +.navbar-expand > .container, .navbar-expand > .container-fluid, .navbar-expand > .container-lg, .navbar-expand > .container-md, .navbar-expand > .container-sm, .navbar-expand > .container-xl { + padding-right: 0; + padding-left: 0 +} + +.navbar-expand .navbar-nav { + -ms-flex-direction: row; + flex-direction: row +} + +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute +} + +.navbar-expand .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem +} + +.navbar-expand > .container, .navbar-expand > .container-fluid, .navbar-expand > .container-lg, .navbar-expand > .container-md, .navbar-expand > .container-sm, .navbar-expand > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap +} + +.navbar-expand .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto +} + +.navbar-expand .navbar-toggler { + display: none +} + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, .9) +} + +.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover { + color: rgba(0, 0, 0, .9) +} + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, .5) +} + +.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover { + color: rgba(0, 0, 0, .7) +} + +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, .3) +} + +.navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.active, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .show > .nav-link { + color: rgba(0, 0, 0, .9) +} + +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, .5); + border-color: rgba(0, 0, 0, .1) +} + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") +} + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, .5) +} + +.navbar-light .navbar-text a { + color: rgba(0, 0, 0, .9) +} + +.navbar-light .navbar-text a:focus, .navbar-light .navbar-text a:hover { + color: rgba(0, 0, 0, .9) +} + +.navbar-dark .navbar-brand { + color: #fff +} + +.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover { + color: #fff +} + +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, .5) +} + +.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover { + color: rgba(255, 255, 255, .75) +} + +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, .25) +} + +.navbar-dark .navbar-nav .active > .nav-link, .navbar-dark .navbar-nav .nav-link.active, .navbar-dark .navbar-nav .nav-link.show, .navbar-dark .navbar-nav .show > .nav-link { + color: #fff +} + +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, .5); + border-color: rgba(255, 255, 255, .1) +} + +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") +} + +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, .5) +} + +.navbar-dark .navbar-text a { + color: #fff +} + +.navbar-dark .navbar-text a:focus, .navbar-dark .navbar-text a:hover { + color: #fff +} + +.card { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, .125); + border-radius: .25rem +} + +.card > hr { + margin-right: 0; + margin-left: 0 +} + +.card > .list-group { + border-top: inherit; + border-bottom: inherit +} + +.card > .list-group:first-child { + border-top-width: 0; + border-top-left-radius: calc(.25rem - 1px); + border-top-right-radius: calc(.25rem - 1px) +} + +.card > .list-group:last-child { + border-bottom-width: 0; + border-bottom-right-radius: calc(.25rem - 1px); + border-bottom-left-radius: calc(.25rem - 1px) +} + +.card > .card-header + .list-group, .card > .list-group + .card-footer { + border-top: 0 +} + +.card-body { + -ms-flex: 1 1 auto; + flex: 1 1 auto; + min-height: 1px; + padding: 1.25rem +} + +.card-title { + margin-bottom: .75rem +} + +.card-subtitle { + margin-top: -.375rem; + margin-bottom: 0 +} + +.card-text:last-child { + margin-bottom: 0 +} + +.card-link:hover { + text-decoration: none +} + +.card-link + .card-link { + margin-left: 1.25rem +} + +.card-header { + padding: .75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, .03); + border-bottom: 1px solid rgba(0, 0, 0, .125) +} + +.card-header:first-child { + border-radius: calc(.25rem - 1px) calc(.25rem - 1px) 0 0 +} + +.card-footer { + padding: .75rem 1.25rem; + background-color: rgba(0, 0, 0, .03); + border-top: 1px solid rgba(0, 0, 0, .125) +} + +.card-footer:last-child { + border-radius: 0 0 calc(.25rem - 1px) calc(.25rem - 1px) +} + +.card-header-tabs { + margin-right: -.625rem; + margin-bottom: -.75rem; + margin-left: -.625rem; + border-bottom: 0 +} + +.card-header-pills { + margin-right: -.625rem; + margin-left: -.625rem +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; + border-radius: calc(.25rem - 1px) +} + +.card-img, .card-img-bottom, .card-img-top { + -ms-flex-negative: 0; + flex-shrink: 0; + width: 100% +} + +.card-img, .card-img-top { + border-top-left-radius: calc(.25rem - 1px); + border-top-right-radius: calc(.25rem - 1px) +} + +.card-img, .card-img-bottom { + border-bottom-right-radius: calc(.25rem - 1px); + border-bottom-left-radius: calc(.25rem - 1px) +} + +.card-deck .card { + margin-bottom: 15px +} + +@media (min-width: 576px) { + .card-deck { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px + } + + .card-deck .card { + -ms-flex: 1 0 0%; + flex: 1 0 0%; + margin-right: 15px; + margin-bottom: 0; + margin-left: 15px + } +} + +.card-group > .card { + margin-bottom: 15px +} + +@media (min-width: 576px) { + .card-group { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap + } + + .card-group > .card { + -ms-flex: 1 0 0%; + flex: 1 0 0%; + margin-bottom: 0 + } + + .card-group > .card + .card { + margin-left: 0; + border-left: 0 + } + + .card-group > .card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0 + } + + .card-group > .card:not(:last-child) .card-header, .card-group > .card:not(:last-child) .card-img-top { + border-top-right-radius: 0 + } + + .card-group > .card:not(:last-child) .card-footer, .card-group > .card:not(:last-child) .card-img-bottom { + border-bottom-right-radius: 0 + } + + .card-group > .card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0 + } + + .card-group > .card:not(:first-child) .card-header, .card-group > .card:not(:first-child) .card-img-top { + border-top-left-radius: 0 + } + + .card-group > .card:not(:first-child) .card-footer, .card-group > .card:not(:first-child) .card-img-bottom { + border-bottom-left-radius: 0 + } +} + +.card-columns .card { + margin-bottom: .75rem +} + +@media (min-width: 576px) { + .card-columns { + -webkit-column-count: 3; + -moz-column-count: 3; + column-count: 3; + -webkit-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + orphans: 1; + widows: 1 + } + + .card-columns .card { + display: inline-block; + width: 100% + } +} + +.accordion { + overflow-anchor: none +} + +.accordion > .card { + overflow: hidden +} + +.accordion > .card:not(:last-of-type) { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0 +} + +.accordion > .card:not(:first-of-type) { + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.accordion > .card > .card-header { + border-radius: 0; + margin-bottom: -1px +} + +.breadcrumb { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding: .75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: .25rem +} + +.breadcrumb-item { + display: -ms-flexbox; + display: flex +} + +.breadcrumb-item + .breadcrumb-item { + padding-left: .5rem +} + +.breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: .5rem; + color: #6c757d; + content: "/" +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none +} + +.breadcrumb-item.active { + color: #6c757d +} + +.pagination { + display: -ms-flexbox; + display: flex; + padding-left: 0; + list-style: none; + border-radius: .25rem +} + +.page-link { + position: relative; + display: block; + padding: .5rem .75rem; + margin-left: -1px; + line-height: 1.25; + color: #007bff; + background-color: #fff; + border: 1px solid #dee2e6 +} + +.page-link:hover { + z-index: 2; + color: #0056b3; + text-decoration: none; + background-color: #e9ecef; + border-color: #dee2e6 +} + +.page-link:focus { + z-index: 3; + outline: 0; + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25) +} + +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: .25rem; + border-bottom-left-radius: .25rem +} + +.page-item:last-child .page-link { + border-top-right-radius: .25rem; + border-bottom-right-radius: .25rem +} + +.page-item.active .page-link { + z-index: 3; + color: #fff; + background-color: #007bff; + border-color: #007bff +} + +.page-item.disabled .page-link { + color: #6c757d; + pointer-events: none; + cursor: auto; + background-color: #fff; + border-color: #dee2e6 +} + +.pagination-lg .page-link { + padding: .75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5 +} + +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: .3rem; + border-bottom-left-radius: .3rem +} + +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: .3rem; + border-bottom-right-radius: .3rem +} + +.pagination-sm .page-link { + padding: .25rem .5rem; + font-size: .875rem; + line-height: 1.5 +} + +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: .2rem; + border-bottom-left-radius: .2rem +} + +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: .2rem; + border-bottom-right-radius: .2rem +} + +.badge { + display: inline-block; + padding: .25em .4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25rem; + transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out +} + +@media (prefers-reduced-motion: reduce) { + .badge { + transition: none + } +} + +a.badge:focus, a.badge:hover { + text-decoration: none +} + +.badge:empty { + display: none +} + +.btn .badge { + position: relative; + top: -1px +} + +.badge-pill { + padding-right: .6em; + padding-left: .6em; + border-radius: 10rem +} + +.badge-primary { + color: #fff; + background-color: #007bff +} + +a.badge-primary:focus, a.badge-primary:hover { + color: #fff; + background-color: #0062cc +} + +a.badge-primary.focus, a.badge-primary:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .5) +} + +.badge-secondary { + color: #fff; + background-color: #6c757d +} + +a.badge-secondary:focus, a.badge-secondary:hover { + color: #fff; + background-color: #545b62 +} + +a.badge-secondary.focus, a.badge-secondary:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(108, 117, 125, .5) +} + +.badge-success { + color: #fff; + background-color: #28a745 +} + +a.badge-success:focus, a.badge-success:hover { + color: #fff; + background-color: #1e7e34 +} + +a.badge-success.focus, a.badge-success:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(40, 167, 69, .5) +} + +.badge-info { + color: #fff; + background-color: #17a2b8 +} + +a.badge-info:focus, a.badge-info:hover { + color: #fff; + background-color: #117a8b +} + +a.badge-info.focus, a.badge-info:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(23, 162, 184, .5) +} + +.badge-warning { + color: #212529; + background-color: #ffc107 +} + +a.badge-warning:focus, a.badge-warning:hover { + color: #212529; + background-color: #d39e00 +} + +a.badge-warning.focus, a.badge-warning:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(255, 193, 7, .5) +} + +.badge-danger { + color: #fff; + background-color: #dc3545 +} + +a.badge-danger:focus, a.badge-danger:hover { + color: #fff; + background-color: #bd2130 +} + +a.badge-danger.focus, a.badge-danger:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(220, 53, 69, .5) +} + +.badge-light { + color: #212529; + background-color: #f8f9fa +} + +a.badge-light:focus, a.badge-light:hover { + color: #212529; + background-color: #dae0e5 +} + +a.badge-light.focus, a.badge-light:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(248, 249, 250, .5) +} + +.badge-dark { + color: #fff; + background-color: #343a40 +} + +a.badge-dark:focus, a.badge-dark:hover { + color: #fff; + background-color: #1d2124 +} + +a.badge-dark.focus, a.badge-dark:focus { + outline: 0; + box-shadow: 0 0 0 .2rem rgba(52, 58, 64, .5) +} + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: .3rem +} + +@media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem + } +} + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0 +} + +.alert { + position: relative; + padding: .75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: .25rem +} + +.alert-heading { + color: inherit +} + +.alert-link { + font-weight: 700 +} + +.alert-dismissible { + padding-right: 4rem +} + +.alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: .75rem 1.25rem; + color: inherit +} + +.alert-primary { + color: #004085; + background-color: #cce5ff; + border-color: #b8daff +} + +.alert-primary hr { + border-top-color: #9fcdff +} + +.alert-primary .alert-link { + color: #002752 +} + +.alert-secondary { + color: #383d41; + background-color: #e2e3e5; + border-color: #d6d8db +} + +.alert-secondary hr { + border-top-color: #c8cbcf +} + +.alert-secondary .alert-link { + color: #202326 +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb +} + +.alert-success hr { + border-top-color: #b1dfbb +} + +.alert-success .alert-link { + color: #0b2e13 +} + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb +} + +.alert-info hr { + border-top-color: #abdde5 +} + +.alert-info .alert-link { + color: #062c33 +} + +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeeba +} + +.alert-warning hr { + border-top-color: #ffe8a1 +} + +.alert-warning .alert-link { + color: #533f03 +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb +} + +.alert-danger hr { + border-top-color: #f1b0b7 +} + +.alert-danger .alert-link { + color: #491217 +} + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe +} + +.alert-light hr { + border-top-color: #ececf6 +} + +.alert-light .alert-link { + color: #686868 +} + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca +} + +.alert-dark hr { + border-top-color: #b9bbbe +} + +.alert-dark .alert-link { + color: #040505 +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 1rem 0 + } + to { + background-position: 0 0 + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0 + } + to { + background-position: 0 0 + } +} + +.progress { + display: -ms-flexbox; + display: flex; + height: 1rem; + overflow: hidden; + line-height: 0; + font-size: .75rem; + background-color: #e9ecef; + border-radius: .25rem +} + +.progress-bar { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: center; + justify-content: center; + overflow: hidden; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #007bff; + transition: width .6s ease +} + +@media (prefers-reduced-motion: reduce) { + .progress-bar { + transition: none + } +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-size: 1rem 1rem +} + +.progress-bar-animated { + -webkit-animation: progress-bar-stripes 1s linear infinite; + animation: progress-bar-stripes 1s linear infinite +} + +@media (prefers-reduced-motion: reduce) { + .progress-bar-animated { + -webkit-animation: none; + animation: none + } +} + +.media { + display: -ms-flexbox; + display: flex; + -ms-flex-align: start; + align-items: flex-start +} + +.media-body { + -ms-flex: 1; + flex: 1 +} + +.list-group { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + border-radius: .25rem +} + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit +} + +.list-group-item-action:focus, .list-group-item-action:hover { + z-index: 1; + color: #495057; + text-decoration: none; + background-color: #f8f9fa +} + +.list-group-item-action:active { + color: #212529; + background-color: #e9ecef +} + +.list-group-item { + position: relative; + display: block; + padding: .75rem 1.25rem; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, .125) +} + +.list-group-item:first-child { + border-top-left-radius: inherit; + border-top-right-radius: inherit +} + +.list-group-item:last-child { + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit +} + +.list-group-item.disabled, .list-group-item:disabled { + color: #6c757d; + pointer-events: none; + background-color: #fff +} + +.list-group-item.active { + z-index: 2; + color: #fff; + background-color: #007bff; + border-color: #007bff +} + +.list-group-item + .list-group-item { + border-top-width: 0 +} + +.list-group-item + .list-group-item.active { + margin-top: -1px; + border-top-width: 1px +} + +.list-group-horizontal { + -ms-flex-direction: row; + flex-direction: row +} + +.list-group-horizontal > .list-group-item:first-child { + border-bottom-left-radius: .25rem; + border-top-right-radius: 0 +} + +.list-group-horizontal > .list-group-item:last-child { + border-top-right-radius: .25rem; + border-bottom-left-radius: 0 +} + +.list-group-horizontal > .list-group-item.active { + margin-top: 0 +} + +.list-group-horizontal > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0 +} + +.list-group-horizontal > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px +} + +@media (min-width: 576px) { + .list-group-horizontal-sm { + -ms-flex-direction: row; + flex-direction: row + } + + .list-group-horizontal-sm > .list-group-item:first-child { + border-bottom-left-radius: .25rem; + border-top-right-radius: 0 + } + + .list-group-horizontal-sm > .list-group-item:last-child { + border-top-right-radius: .25rem; + border-bottom-left-radius: 0 + } + + .list-group-horizontal-sm > .list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-sm > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0 + } + + .list-group-horizontal-sm > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px + } +} + +@media (min-width: 768px) { + .list-group-horizontal-md { + -ms-flex-direction: row; + flex-direction: row + } + + .list-group-horizontal-md > .list-group-item:first-child { + border-bottom-left-radius: .25rem; + border-top-right-radius: 0 + } + + .list-group-horizontal-md > .list-group-item:last-child { + border-top-right-radius: .25rem; + border-bottom-left-radius: 0 + } + + .list-group-horizontal-md > .list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-md > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0 + } + + .list-group-horizontal-md > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px + } +} + +@media (min-width: 992px) { + .list-group-horizontal-lg { + -ms-flex-direction: row; + flex-direction: row + } + + .list-group-horizontal-lg > .list-group-item:first-child { + border-bottom-left-radius: .25rem; + border-top-right-radius: 0 + } + + .list-group-horizontal-lg > .list-group-item:last-child { + border-top-right-radius: .25rem; + border-bottom-left-radius: 0 + } + + .list-group-horizontal-lg > .list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-lg > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0 + } + + .list-group-horizontal-lg > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px + } +} + +@media (min-width: 1200px) { + .list-group-horizontal-xl { + -ms-flex-direction: row; + flex-direction: row + } + + .list-group-horizontal-xl > .list-group-item:first-child { + border-bottom-left-radius: .25rem; + border-top-right-radius: 0 + } + + .list-group-horizontal-xl > .list-group-item:last-child { + border-top-right-radius: .25rem; + border-bottom-left-radius: 0 + } + + .list-group-horizontal-xl > .list-group-item.active { + margin-top: 0 + } + + .list-group-horizontal-xl > .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0 + } + + .list-group-horizontal-xl > .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px + } +} + +.list-group-flush { + border-radius: 0 +} + +.list-group-flush > .list-group-item { + border-width: 0 0 1px +} + +.list-group-flush > .list-group-item:last-child { + border-bottom-width: 0 +} + +.list-group-item-primary { + color: #004085; + background-color: #b8daff +} + +.list-group-item-primary.list-group-item-action:focus, .list-group-item-primary.list-group-item-action:hover { + color: #004085; + background-color: #9fcdff +} + +.list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #004085; + border-color: #004085 +} + +.list-group-item-secondary { + color: #383d41; + background-color: #d6d8db +} + +.list-group-item-secondary.list-group-item-action:focus, .list-group-item-secondary.list-group-item-action:hover { + color: #383d41; + background-color: #c8cbcf +} + +.list-group-item-secondary.list-group-item-action.active { + color: #fff; + background-color: #383d41; + border-color: #383d41 +} + +.list-group-item-success { + color: #155724; + background-color: #c3e6cb +} + +.list-group-item-success.list-group-item-action:focus, .list-group-item-success.list-group-item-action:hover { + color: #155724; + background-color: #b1dfbb +} + +.list-group-item-success.list-group-item-action.active { + color: #fff; + background-color: #155724; + border-color: #155724 +} + +.list-group-item-info { + color: #0c5460; + background-color: #bee5eb +} + +.list-group-item-info.list-group-item-action:focus, .list-group-item-info.list-group-item-action:hover { + color: #0c5460; + background-color: #abdde5 +} + +.list-group-item-info.list-group-item-action.active { + color: #fff; + background-color: #0c5460; + border-color: #0c5460 +} + +.list-group-item-warning { + color: #856404; + background-color: #ffeeba +} + +.list-group-item-warning.list-group-item-action:focus, .list-group-item-warning.list-group-item-action:hover { + color: #856404; + background-color: #ffe8a1 +} + +.list-group-item-warning.list-group-item-action.active { + color: #fff; + background-color: #856404; + border-color: #856404 +} + +.list-group-item-danger { + color: #721c24; + background-color: #f5c6cb +} + +.list-group-item-danger.list-group-item-action:focus, .list-group-item-danger.list-group-item-action:hover { + color: #721c24; + background-color: #f1b0b7 +} + +.list-group-item-danger.list-group-item-action.active { + color: #fff; + background-color: #721c24; + border-color: #721c24 +} + +.list-group-item-light { + color: #818182; + background-color: #fdfdfe +} + +.list-group-item-light.list-group-item-action:focus, .list-group-item-light.list-group-item-action:hover { + color: #818182; + background-color: #ececf6 +} + +.list-group-item-light.list-group-item-action.active { + color: #fff; + background-color: #818182; + border-color: #818182 +} + +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca +} + +.list-group-item-dark.list-group-item-action:focus, .list-group-item-dark.list-group-item-action:hover { + color: #1b1e21; + background-color: #b9bbbe +} + +.list-group-item-dark.list-group-item-action.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21 +} + +.close { + float: right; + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5 +} + +.close:hover { + color: #000; + text-decoration: none +} + +.close:not(:disabled):not(.disabled):focus, .close:not(:disabled):not(.disabled):hover { + opacity: .75 +} + +button.close { + padding: 0; + background-color: transparent; + border: 0 +} + +a.close.disabled { + pointer-events: none +} + +.toast { + -ms-flex-preferred-size: 350px; + flex-basis: 350px; + max-width: 350px; + font-size: .875rem; + background-color: rgba(255, 255, 255, .85); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, .1); + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .1); + opacity: 0; + border-radius: .25rem +} + +.toast:not(:last-child) { + margin-bottom: .75rem +} + +.toast.showing { + opacity: 1 +} + +.toast.show { + display: block; + opacity: 1 +} + +.toast.hide { + display: none +} + +.toast-header { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding: .25rem .75rem; + color: #6c757d; + background-color: rgba(255, 255, 255, .85); + background-clip: padding-box; + border-bottom: 1px solid rgba(0, 0, 0, .05); + border-top-left-radius: calc(.25rem - 1px); + border-top-right-radius: calc(.25rem - 1px) +} + +.toast-body { + padding: .75rem +} + +.modal-open { + overflow: hidden +} + +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto +} + +.modal { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + display: none; + width: 100%; + height: 100%; + overflow: hidden; + outline: 0 +} + +.modal-dialog { + position: relative; + width: auto; + margin: .5rem; + pointer-events: none +} + +.modal.fade .modal-dialog { + transition: -webkit-transform .3s ease-out; + transition: transform .3s ease-out; + transition: transform .3s ease-out, -webkit-transform .3s ease-out; + -webkit-transform: translate(0, -50px); + transform: translate(0, -50px) +} + +@media (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + transition: none + } +} + +.modal.show .modal-dialog { + -webkit-transform: none; + transform: none +} + +.modal.modal-static .modal-dialog { + -webkit-transform: scale(1.02); + transform: scale(1.02) +} + +.modal-dialog-scrollable { + display: -ms-flexbox; + display: flex; + max-height: calc(100% - 1rem) +} + +.modal-dialog-scrollable .modal-content { + max-height: calc(100vh - 1rem); + overflow: hidden +} + +.modal-dialog-scrollable .modal-footer, .modal-dialog-scrollable .modal-header { + -ms-flex-negative: 0; + flex-shrink: 0 +} + +.modal-dialog-scrollable .modal-body { + overflow-y: auto +} + +.modal-dialog-centered { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + min-height: calc(100% - 1rem) +} + +.modal-dialog-centered::before { + display: block; + height: calc(100vh - 1rem); + height: -webkit-min-content; + height: -moz-min-content; + height: min-content; + content: "" +} + +.modal-dialog-centered.modal-dialog-scrollable { + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: center; + justify-content: center; + height: 100% +} + +.modal-dialog-centered.modal-dialog-scrollable .modal-content { + max-height: none +} + +.modal-dialog-centered.modal-dialog-scrollable::before { + content: none +} + +.modal-content { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: .3rem; + outline: 0 +} + +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000 +} + +.modal-backdrop.fade { + opacity: 0 +} + +.modal-backdrop.show { + opacity: .5 +} + +.modal-header { + display: -ms-flexbox; + display: flex; + -ms-flex-align: start; + align-items: flex-start; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 1rem 1rem; + border-bottom: 1px solid #dee2e6; + border-top-left-radius: calc(.3rem - 1px); + border-top-right-radius: calc(.3rem - 1px) +} + +.modal-header .close { + padding: 1rem 1rem; + margin: -1rem -1rem -1rem auto +} + +.modal-title { + margin-bottom: 0; + line-height: 1.5 +} + +.modal-body { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1rem +} + +.modal-footer { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: end; + justify-content: flex-end; + padding: .75rem; + border-top: 1px solid #dee2e6; + border-bottom-right-radius: calc(.3rem - 1px); + border-bottom-left-radius: calc(.3rem - 1px) +} + +.modal-footer > * { + margin: .25rem +} + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll +} + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 1.75rem auto + } + + .modal-dialog-scrollable { + max-height: calc(100% - 3.5rem) + } + + .modal-dialog-scrollable .modal-content { + max-height: calc(100vh - 3.5rem) + } + + .modal-dialog-centered { + min-height: calc(100% - 3.5rem) + } + + .modal-dialog-centered::before { + height: calc(100vh - 3.5rem); + height: -webkit-min-content; + height: -moz-min-content; + height: min-content + } + + .modal-sm { + max-width: 300px + } +} + +@media (min-width: 992px) { + .modal-lg, .modal-xl { + max-width: 800px + } +} + +@media (min-width: 1200px) { + .modal-xl { + max-width: 1140px + } +} + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: .875rem; + word-wrap: break-word; + opacity: 0 +} + +.tooltip.show { + opacity: .9 +} + +.tooltip .arrow { + position: absolute; + display: block; + width: .8rem; + height: .4rem +} + +.tooltip .arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid +} + +.bs-tooltip-auto[x-placement^=top], .bs-tooltip-top { + padding: .4rem 0 +} + +.bs-tooltip-auto[x-placement^=top] .arrow, .bs-tooltip-top .arrow { + bottom: 0 +} + +.bs-tooltip-auto[x-placement^=top] .arrow::before, .bs-tooltip-top .arrow::before { + top: 0; + border-width: .4rem .4rem 0; + border-top-color: #000 +} + +.bs-tooltip-auto[x-placement^=right], .bs-tooltip-right { + padding: 0 .4rem +} + +.bs-tooltip-auto[x-placement^=right] .arrow, .bs-tooltip-right .arrow { + left: 0; + width: .4rem; + height: .8rem +} + +.bs-tooltip-auto[x-placement^=right] .arrow::before, .bs-tooltip-right .arrow::before { + right: 0; + border-width: .4rem .4rem .4rem 0; + border-right-color: #000 +} + +.bs-tooltip-auto[x-placement^=bottom], .bs-tooltip-bottom { + padding: .4rem 0 +} + +.bs-tooltip-auto[x-placement^=bottom] .arrow, .bs-tooltip-bottom .arrow { + top: 0 +} + +.bs-tooltip-auto[x-placement^=bottom] .arrow::before, .bs-tooltip-bottom .arrow::before { + bottom: 0; + border-width: 0 .4rem .4rem; + border-bottom-color: #000 +} + +.bs-tooltip-auto[x-placement^=left], .bs-tooltip-left { + padding: 0 .4rem +} + +.bs-tooltip-auto[x-placement^=left] .arrow, .bs-tooltip-left .arrow { + right: 0; + width: .4rem; + height: .8rem +} + +.bs-tooltip-auto[x-placement^=left] .arrow::before, .bs-tooltip-left .arrow::before { + left: 0; + border-width: .4rem 0 .4rem .4rem; + border-left-color: #000 +} + +.tooltip-inner { + max-width: 200px; + padding: .25rem .5rem; + color: #fff; + text-align: center; + background-color: #000; + border-radius: .25rem +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: .875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: .3rem +} + +.popover .arrow { + position: absolute; + display: block; + width: 1rem; + height: .5rem; + margin: 0 .3rem +} + +.popover .arrow::after, .popover .arrow::before { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid +} + +.bs-popover-auto[x-placement^=top], .bs-popover-top { + margin-bottom: .5rem +} + +.bs-popover-auto[x-placement^=top] > .arrow, .bs-popover-top > .arrow { + bottom: calc(-.5rem - 1px) +} + +.bs-popover-auto[x-placement^=top] > .arrow::before, .bs-popover-top > .arrow::before { + bottom: 0; + border-width: .5rem .5rem 0; + border-top-color: rgba(0, 0, 0, .25) +} + +.bs-popover-auto[x-placement^=top] > .arrow::after, .bs-popover-top > .arrow::after { + bottom: 1px; + border-width: .5rem .5rem 0; + border-top-color: #fff +} + +.bs-popover-auto[x-placement^=right], .bs-popover-right { + margin-left: .5rem +} + +.bs-popover-auto[x-placement^=right] > .arrow, .bs-popover-right > .arrow { + left: calc(-.5rem - 1px); + width: .5rem; + height: 1rem; + margin: .3rem 0 +} + +.bs-popover-auto[x-placement^=right] > .arrow::before, .bs-popover-right > .arrow::before { + left: 0; + border-width: .5rem .5rem .5rem 0; + border-right-color: rgba(0, 0, 0, .25) +} + +.bs-popover-auto[x-placement^=right] > .arrow::after, .bs-popover-right > .arrow::after { + left: 1px; + border-width: .5rem .5rem .5rem 0; + border-right-color: #fff +} + +.bs-popover-auto[x-placement^=bottom], .bs-popover-bottom { + margin-top: .5rem +} + +.bs-popover-auto[x-placement^=bottom] > .arrow, .bs-popover-bottom > .arrow { + top: calc(-.5rem - 1px) +} + +.bs-popover-auto[x-placement^=bottom] > .arrow::before, .bs-popover-bottom > .arrow::before { + top: 0; + border-width: 0 .5rem .5rem .5rem; + border-bottom-color: rgba(0, 0, 0, .25) +} + +.bs-popover-auto[x-placement^=bottom] > .arrow::after, .bs-popover-bottom > .arrow::after { + top: 1px; + border-width: 0 .5rem .5rem .5rem; + border-bottom-color: #fff +} + +.bs-popover-auto[x-placement^=bottom] .popover-header::before, .bs-popover-bottom .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 1rem; + margin-left: -.5rem; + content: ""; + border-bottom: 1px solid #f7f7f7 +} + +.bs-popover-auto[x-placement^=left], .bs-popover-left { + margin-right: .5rem +} + +.bs-popover-auto[x-placement^=left] > .arrow, .bs-popover-left > .arrow { + right: calc(-.5rem - 1px); + width: .5rem; + height: 1rem; + margin: .3rem 0 +} + +.bs-popover-auto[x-placement^=left] > .arrow::before, .bs-popover-left > .arrow::before { + right: 0; + border-width: .5rem 0 .5rem .5rem; + border-left-color: rgba(0, 0, 0, .25) +} + +.bs-popover-auto[x-placement^=left] > .arrow::after, .bs-popover-left > .arrow::after { + right: 1px; + border-width: .5rem 0 .5rem .5rem; + border-left-color: #fff +} + +.popover-header { + padding: .5rem .75rem; + margin-bottom: 0; + font-size: 1rem; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(.3rem - 1px); + border-top-right-radius: calc(.3rem - 1px) +} + +.popover-header:empty { + display: none +} + +.popover-body { + padding: .5rem .75rem; + color: #212529 +} + +.carousel { + position: relative +} + +.carousel.pointer-event { + -ms-touch-action: pan-y; + touch-action: pan-y +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden +} + +.carousel-inner::after { + display: block; + clear: both; + content: "" +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: -webkit-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + transition: transform .6s ease-in-out, -webkit-transform .6s ease-in-out +} + +@media (prefers-reduced-motion: reduce) { + .carousel-item { + transition: none + } +} + +.carousel-item-next, .carousel-item-prev, .carousel-item.active { + display: block +} + +.active.carousel-item-right, .carousel-item-next:not(.carousel-item-left) { + -webkit-transform: translateX(100%); + transform: translateX(100%) +} + +.active.carousel-item-left, .carousel-item-prev:not(.carousel-item-right) { + -webkit-transform: translateX(-100%); + transform: translateX(-100%) +} + +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + -webkit-transform: none; + transform: none +} + +.carousel-fade .carousel-item-next.carousel-item-left, .carousel-fade .carousel-item-prev.carousel-item-right, .carousel-fade .carousel-item.active { + z-index: 1; + opacity: 1 +} + +.carousel-fade .active.carousel-item-left, .carousel-fade .active.carousel-item-right { + z-index: 0; + opacity: 0; + transition: opacity 0s .6s +} + +@media (prefers-reduced-motion: reduce) { + .carousel-fade .active.carousel-item-left, .carousel-fade .active.carousel-item-right { + transition: none + } +} + +.carousel-control-next, .carousel-control-prev { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: .5; + transition: opacity .15s ease +} + +@media (prefers-reduced-motion: reduce) { + .carousel-control-next, .carousel-control-prev { + transition: none + } +} + +.carousel-control-next:focus, .carousel-control-next:hover, .carousel-control-prev:focus, .carousel-control-prev:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: .9 +} + +.carousel-control-prev { + left: 0 +} + +.carousel-control-next { + right: 0 +} + +.carousel-control-next-icon, .carousel-control-prev-icon { + display: inline-block; + width: 20px; + height: 20px; + background: no-repeat 50%/100% 100% +} + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e") +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e") +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 15; + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none +} + +.carousel-indicators li { + box-sizing: content-box; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: .5; + transition: opacity .6s ease +} + +@media (prefers-reduced-motion: reduce) { + .carousel-indicators li { + transition: none + } +} + +.carousel-indicators .active { + opacity: 1 +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center +} + +@-webkit-keyframes spinner-border { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg) + } +} + +@keyframes spinner-border { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg) + } +} + +.spinner-border { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + border: .25em solid currentColor; + border-right-color: transparent; + border-radius: 50%; + -webkit-animation: spinner-border .75s linear infinite; + animation: spinner-border .75s linear infinite +} + +.spinner-border-sm { + width: 1rem; + height: 1rem; + border-width: .2em +} + +@-webkit-keyframes spinner-grow { + 0% { + -webkit-transform: scale(0); + transform: scale(0) + } + 50% { + opacity: 1; + -webkit-transform: none; + transform: none + } +} + +@keyframes spinner-grow { + 0% { + -webkit-transform: scale(0); + transform: scale(0) + } + 50% { + opacity: 1; + -webkit-transform: none; + transform: none + } +} + +.spinner-grow { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + background-color: currentColor; + border-radius: 50%; + opacity: 0; + -webkit-animation: spinner-grow .75s linear infinite; + animation: spinner-grow .75s linear infinite +} + +.spinner-grow-sm { + width: 1rem; + height: 1rem +} + +.align-baseline { + vertical-align: baseline !important +} + +.align-top { + vertical-align: top !important +} + +.align-middle { + vertical-align: middle !important +} + +.align-bottom { + vertical-align: bottom !important +} + +.align-text-bottom { + vertical-align: text-bottom !important +} + +.align-text-top { + vertical-align: text-top !important +} + +.bg-primary { + background-color: #007bff !important +} + +a.bg-primary:focus, a.bg-primary:hover, button.bg-primary:focus, button.bg-primary:hover { + background-color: #0062cc !important +} + +.bg-secondary { + background-color: #6c757d !important +} + +a.bg-secondary:focus, a.bg-secondary:hover, button.bg-secondary:focus, button.bg-secondary:hover { + background-color: #545b62 !important +} + +.bg-success { + background-color: #28a745 !important +} + +a.bg-success:focus, a.bg-success:hover, button.bg-success:focus, button.bg-success:hover { + background-color: #1e7e34 !important +} + +.bg-info { + background-color: #17a2b8 !important +} + +a.bg-info:focus, a.bg-info:hover, button.bg-info:focus, button.bg-info:hover { + background-color: #117a8b !important +} + +.bg-warning { + background-color: #ffc107 !important +} + +a.bg-warning:focus, a.bg-warning:hover, button.bg-warning:focus, button.bg-warning:hover { + background-color: #d39e00 !important +} + +.bg-danger { + background-color: #dc3545 !important +} + +a.bg-danger:focus, a.bg-danger:hover, button.bg-danger:focus, button.bg-danger:hover { + background-color: #bd2130 !important +} + +.bg-light { + background-color: #f8f9fa !important +} + +a.bg-light:focus, a.bg-light:hover, button.bg-light:focus, button.bg-light:hover { + background-color: #dae0e5 !important +} + +.bg-dark { + background-color: #343a40 !important +} + +a.bg-dark:focus, a.bg-dark:hover, button.bg-dark:focus, button.bg-dark:hover { + background-color: #1d2124 !important +} + +.bg-white { + background-color: #fff !important +} + +.bg-transparent { + background-color: transparent !important +} + +.border { + border: 1px solid #dee2e6 !important +} + +.border-top { + border-top: 1px solid #dee2e6 !important +} + +.border-right { + border-right: 1px solid #dee2e6 !important +} + +.border-bottom { + border-bottom: 1px solid #dee2e6 !important +} + +.border-left { + border-left: 1px solid #dee2e6 !important +} + +.border-0 { + border: 0 !important +} + +.border-top-0 { + border-top: 0 !important +} + +.border-right-0 { + border-right: 0 !important +} + +.border-bottom-0 { + border-bottom: 0 !important +} + +.border-left-0 { + border-left: 0 !important +} + +.border-primary { + border-color: #007bff !important +} + +.border-secondary { + border-color: #6c757d !important +} + +.border-success { + border-color: #28a745 !important +} + +.border-info { + border-color: #17a2b8 !important +} + +.border-warning { + border-color: #ffc107 !important +} + +.border-danger { + border-color: #dc3545 !important +} + +.border-light { + border-color: #f8f9fa !important +} + +.border-dark { + border-color: #343a40 !important +} + +.border-white { + border-color: #fff !important +} + +.rounded-sm { + border-radius: .2rem !important +} + +.rounded { + border-radius: .25rem !important +} + +.rounded-top { + border-top-left-radius: .25rem !important; + border-top-right-radius: .25rem !important +} + +.rounded-right { + border-top-right-radius: .25rem !important; + border-bottom-right-radius: .25rem !important +} + +.rounded-bottom { + border-bottom-right-radius: .25rem !important; + border-bottom-left-radius: .25rem !important +} + +.rounded-left { + border-top-left-radius: .25rem !important; + border-bottom-left-radius: .25rem !important +} + +.rounded-lg { + border-radius: .3rem !important +} + +.rounded-circle { + border-radius: 50% !important +} + +.rounded-pill { + border-radius: 50rem !important +} + +.rounded-0 { + border-radius: 0 !important +} + +.clearfix::after { + display: block; + clear: both; + content: "" +} + +.d-none { + display: none !important +} + +.d-inline { + display: inline !important +} + +.d-inline-block { + display: inline-block !important +} + +.d-block { + display: block !important +} + +.d-table { + display: table !important +} + +.d-table-row { + display: table-row !important +} + +.d-table-cell { + display: table-cell !important +} + +.d-flex { + display: -ms-flexbox !important; + display: flex !important +} + +.d-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important +} + +@media (min-width: 576px) { + .d-sm-none { + display: none !important + } + + .d-sm-inline { + display: inline !important + } + + .d-sm-inline-block { + display: inline-block !important + } + + .d-sm-block { + display: block !important + } + + .d-sm-table { + display: table !important + } + + .d-sm-table-row { + display: table-row !important + } + + .d-sm-table-cell { + display: table-cell !important + } + + .d-sm-flex { + display: -ms-flexbox !important; + display: flex !important + } + + .d-sm-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important + } +} + +@media (min-width: 768px) { + .d-md-none { + display: none !important + } + + .d-md-inline { + display: inline !important + } + + .d-md-inline-block { + display: inline-block !important + } + + .d-md-block { + display: block !important + } + + .d-md-table { + display: table !important + } + + .d-md-table-row { + display: table-row !important + } + + .d-md-table-cell { + display: table-cell !important + } + + .d-md-flex { + display: -ms-flexbox !important; + display: flex !important + } + + .d-md-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important + } +} + +@media (min-width: 992px) { + .d-lg-none { + display: none !important + } + + .d-lg-inline { + display: inline !important + } + + .d-lg-inline-block { + display: inline-block !important + } + + .d-lg-block { + display: block !important + } + + .d-lg-table { + display: table !important + } + + .d-lg-table-row { + display: table-row !important + } + + .d-lg-table-cell { + display: table-cell !important + } + + .d-lg-flex { + display: -ms-flexbox !important; + display: flex !important + } + + .d-lg-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important + } +} + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important + } + + .d-xl-inline { + display: inline !important + } + + .d-xl-inline-block { + display: inline-block !important + } + + .d-xl-block { + display: block !important + } + + .d-xl-table { + display: table !important + } + + .d-xl-table-row { + display: table-row !important + } + + .d-xl-table-cell { + display: table-cell !important + } + + .d-xl-flex { + display: -ms-flexbox !important; + display: flex !important + } + + .d-xl-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important + } +} + +@media print { + .d-print-none { + display: none !important + } + + .d-print-inline { + display: inline !important + } + + .d-print-inline-block { + display: inline-block !important + } + + .d-print-block { + display: block !important + } + + .d-print-table { + display: table !important + } + + .d-print-table-row { + display: table-row !important + } + + .d-print-table-cell { + display: table-cell !important + } + + .d-print-flex { + display: -ms-flexbox !important; + display: flex !important + } + + .d-print-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important + } +} + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden +} + +.embed-responsive::before { + display: block; + content: "" +} + +.embed-responsive .embed-responsive-item, .embed-responsive embed, .embed-responsive iframe, .embed-responsive object, .embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0 +} + +.embed-responsive-21by9::before { + padding-top: 42.857143% +} + +.embed-responsive-16by9::before { + padding-top: 56.25% +} + +.embed-responsive-4by3::before { + padding-top: 75% +} + +.embed-responsive-1by1::before { + padding-top: 100% +} + +.flex-row { + -ms-flex-direction: row !important; + flex-direction: row !important +} + +.flex-column { + -ms-flex-direction: column !important; + flex-direction: column !important +} + +.flex-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important +} + +.flex-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important +} + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important +} + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important +} + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important +} + +.flex-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important +} + +.flex-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important +} + +.flex-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important +} + +.flex-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important +} + +.flex-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important +} + +.justify-content-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important +} + +.justify-content-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important +} + +.justify-content-center { + -ms-flex-pack: center !important; + justify-content: center !important +} + +.justify-content-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important +} + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important +} + +.align-items-start { + -ms-flex-align: start !important; + align-items: flex-start !important +} + +.align-items-end { + -ms-flex-align: end !important; + align-items: flex-end !important +} + +.align-items-center { + -ms-flex-align: center !important; + align-items: center !important +} + +.align-items-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important +} + +.align-items-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important +} + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important +} + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important +} + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important +} + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important +} + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important +} + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important +} + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important +} + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important +} + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important +} + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important +} + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important +} + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important +} + +@media (min-width: 576px) { + .flex-sm-row { + -ms-flex-direction: row !important; + flex-direction: row !important + } + + .flex-sm-column { + -ms-flex-direction: column !important; + flex-direction: column !important + } + + .flex-sm-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important + } + + .flex-sm-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important + } + + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important + } + + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important + } + + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important + } + + .flex-sm-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important + } + + .flex-sm-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important + } + + .flex-sm-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important + } + + .flex-sm-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important + } + + .flex-sm-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important + } + + .justify-content-sm-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important + } + + .justify-content-sm-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important + } + + .justify-content-sm-center { + -ms-flex-pack: center !important; + justify-content: center !important + } + + .justify-content-sm-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important + } + + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important + } + + .align-items-sm-start { + -ms-flex-align: start !important; + align-items: flex-start !important + } + + .align-items-sm-end { + -ms-flex-align: end !important; + align-items: flex-end !important + } + + .align-items-sm-center { + -ms-flex-align: center !important; + align-items: center !important + } + + .align-items-sm-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important + } + + .align-items-sm-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important + } + + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important + } + + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important + } + + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important + } + + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important + } + + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important + } + + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important + } + + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important + } + + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important + } + + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important + } + + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important + } + + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important + } + + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important + } +} + +@media (min-width: 768px) { + .flex-md-row { + -ms-flex-direction: row !important; + flex-direction: row !important + } + + .flex-md-column { + -ms-flex-direction: column !important; + flex-direction: column !important + } + + .flex-md-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important + } + + .flex-md-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important + } + + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important + } + + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important + } + + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important + } + + .flex-md-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important + } + + .flex-md-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important + } + + .flex-md-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important + } + + .flex-md-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important + } + + .flex-md-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important + } + + .justify-content-md-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important + } + + .justify-content-md-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important + } + + .justify-content-md-center { + -ms-flex-pack: center !important; + justify-content: center !important + } + + .justify-content-md-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important + } + + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important + } + + .align-items-md-start { + -ms-flex-align: start !important; + align-items: flex-start !important + } + + .align-items-md-end { + -ms-flex-align: end !important; + align-items: flex-end !important + } + + .align-items-md-center { + -ms-flex-align: center !important; + align-items: center !important + } + + .align-items-md-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important + } + + .align-items-md-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important + } + + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important + } + + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important + } + + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important + } + + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important + } + + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important + } + + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important + } + + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important + } + + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important + } + + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important + } + + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important + } + + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important + } + + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important + } +} + +@media (min-width: 992px) { + .flex-lg-row { + -ms-flex-direction: row !important; + flex-direction: row !important + } + + .flex-lg-column { + -ms-flex-direction: column !important; + flex-direction: column !important + } + + .flex-lg-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important + } + + .flex-lg-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important + } + + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important + } + + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important + } + + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important + } + + .flex-lg-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important + } + + .flex-lg-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important + } + + .flex-lg-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important + } + + .flex-lg-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important + } + + .flex-lg-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important + } + + .justify-content-lg-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important + } + + .justify-content-lg-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important + } + + .justify-content-lg-center { + -ms-flex-pack: center !important; + justify-content: center !important + } + + .justify-content-lg-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important + } + + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important + } + + .align-items-lg-start { + -ms-flex-align: start !important; + align-items: flex-start !important + } + + .align-items-lg-end { + -ms-flex-align: end !important; + align-items: flex-end !important + } + + .align-items-lg-center { + -ms-flex-align: center !important; + align-items: center !important + } + + .align-items-lg-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important + } + + .align-items-lg-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important + } + + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important + } + + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important + } + + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important + } + + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important + } + + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important + } + + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important + } + + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important + } + + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important + } + + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important + } + + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important + } + + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important + } + + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important + } +} + +@media (min-width: 1200px) { + .flex-xl-row { + -ms-flex-direction: row !important; + flex-direction: row !important + } + + .flex-xl-column { + -ms-flex-direction: column !important; + flex-direction: column !important + } + + .flex-xl-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important + } + + .flex-xl-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important + } + + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important + } + + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important + } + + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important + } + + .flex-xl-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important + } + + .flex-xl-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important + } + + .flex-xl-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important + } + + .flex-xl-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important + } + + .flex-xl-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important + } + + .justify-content-xl-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important + } + + .justify-content-xl-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important + } + + .justify-content-xl-center { + -ms-flex-pack: center !important; + justify-content: center !important + } + + .justify-content-xl-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important + } + + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important + } + + .align-items-xl-start { + -ms-flex-align: start !important; + align-items: flex-start !important + } + + .align-items-xl-end { + -ms-flex-align: end !important; + align-items: flex-end !important + } + + .align-items-xl-center { + -ms-flex-align: center !important; + align-items: center !important + } + + .align-items-xl-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important + } + + .align-items-xl-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important + } + + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important + } + + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important + } + + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important + } + + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important + } + + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important + } + + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important + } + + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important + } + + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important + } + + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important + } + + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important + } + + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important + } + + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important + } +} + +.float-left { + float: left !important +} + +.float-right { + float: right !important +} + +.float-none { + float: none !important +} + +@media (min-width: 576px) { + .float-sm-left { + float: left !important + } + + .float-sm-right { + float: right !important + } + + .float-sm-none { + float: none !important + } +} + +@media (min-width: 768px) { + .float-md-left { + float: left !important + } + + .float-md-right { + float: right !important + } + + .float-md-none { + float: none !important + } +} + +@media (min-width: 992px) { + .float-lg-left { + float: left !important + } + + .float-lg-right { + float: right !important + } + + .float-lg-none { + float: none !important + } +} + +@media (min-width: 1200px) { + .float-xl-left { + float: left !important + } + + .float-xl-right { + float: right !important + } + + .float-xl-none { + float: none !important + } +} + +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + -ms-user-select: all !important; + user-select: all !important +} + +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + -ms-user-select: auto !important; + user-select: auto !important +} + +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important +} + +.overflow-auto { + overflow: auto !important +} + +.overflow-hidden { + overflow: hidden !important +} + +.position-static { + position: static !important +} + +.position-relative { + position: relative !important +} + +.position-absolute { + position: absolute !important +} + +.position-fixed { + position: fixed !important +} + +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030 +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030 +} + +@supports ((position:-webkit-sticky) or (position:sticky)) { + .sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020 + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0 +} + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal +} + +.shadow-sm { + box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .075) !important +} + +.shadow { + box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15) !important +} + +.shadow-lg { + box-shadow: 0 1rem 3rem rgba(0, 0, 0, .175) !important +} + +.shadow-none { + box-shadow: none !important +} + +.w-25 { + width: 25% !important +} + +.w-50 { + width: 50% !important +} + +.w-75 { + width: 75% !important +} + +.w-100 { + width: 100% !important +} + +.w-auto { + width: auto !important +} + +.h-25 { + height: 25% !important +} + +.h-50 { + height: 50% !important +} + +.h-75 { + height: 75% !important +} + +.h-100 { + height: 100% !important +} + +.h-auto { + height: auto !important +} + +.mw-100 { + max-width: 100% !important +} + +.mh-100 { + max-height: 100% !important +} + +.min-vw-100 { + min-width: 100vw !important +} + +.min-vh-100 { + min-height: 100vh !important +} + +.vw-100 { + width: 100vw !important +} + +.vh-100 { + height: 100vh !important +} + +.m-0 { + margin: 0 !important +} + +.mt-0, .my-0 { + margin-top: 0 !important +} + +.mr-0, .mx-0 { + margin-right: 0 !important +} + +.mb-0, .my-0 { + margin-bottom: 0 !important +} + +.ml-0, .mx-0 { + margin-left: 0 !important +} + +.m-1 { + margin: .25rem !important +} + +.mt-1, .my-1 { + margin-top: .25rem !important +} + +.mr-1, .mx-1 { + margin-right: .25rem !important +} + +.mb-1, .my-1 { + margin-bottom: .25rem !important +} + +.ml-1, .mx-1 { + margin-left: .25rem !important +} + +.m-2 { + margin: .5rem !important +} + +.mt-2, .my-2 { + margin-top: .5rem !important +} + +.mr-2, .mx-2 { + margin-right: .5rem !important +} + +.mb-2, .my-2 { + margin-bottom: .5rem !important +} + +.ml-2, .mx-2 { + margin-left: .5rem !important +} + +.m-3 { + margin: 1rem !important +} + +.mt-3, .my-3 { + margin-top: 1rem !important +} + +.mr-3, .mx-3 { + margin-right: 1rem !important +} + +.mb-3, .my-3 { + margin-bottom: 1rem !important +} + +.ml-3, .mx-3 { + margin-left: 1rem !important +} + +.m-4 { + margin: 1.5rem !important +} + +.mt-4, .my-4 { + margin-top: 1.5rem !important +} + +.mr-4, .mx-4 { + margin-right: 1.5rem !important +} + +.mb-4, .my-4 { + margin-bottom: 1.5rem !important +} + +.ml-4, .mx-4 { + margin-left: 1.5rem !important +} + +.m-5 { + margin: 3rem !important +} + +.mt-5, .my-5 { + margin-top: 3rem !important +} + +.mr-5, .mx-5 { + margin-right: 3rem !important +} + +.mb-5, .my-5 { + margin-bottom: 3rem !important +} + +.ml-5, .mx-5 { + margin-left: 3rem !important +} + +.p-0 { + padding: 0 !important +} + +.pt-0, .py-0 { + padding-top: 0 !important +} + +.pr-0, .px-0 { + padding-right: 0 !important +} + +.pb-0, .py-0 { + padding-bottom: 0 !important +} + +.pl-0, .px-0 { + padding-left: 0 !important +} + +.p-1 { + padding: .25rem !important +} + +.pt-1, .py-1 { + padding-top: .25rem !important +} + +.pr-1, .px-1 { + padding-right: .25rem !important +} + +.pb-1, .py-1 { + padding-bottom: .25rem !important +} + +.pl-1, .px-1 { + padding-left: .25rem !important +} + +.p-2 { + padding: .5rem !important +} + +.pt-2, .py-2 { + padding-top: .5rem !important +} + +.pr-2, .px-2 { + padding-right: .5rem !important +} + +.pb-2, .py-2 { + padding-bottom: .5rem !important +} + +.pl-2, .px-2 { + padding-left: .5rem !important +} + +.p-3 { + padding: 1rem !important +} + +.pt-3, .py-3 { + padding-top: 1rem !important +} + +.pr-3, .px-3 { + padding-right: 1rem !important +} + +.pb-3, .py-3 { + padding-bottom: 1rem !important +} + +.pl-3, .px-3 { + padding-left: 1rem !important +} + +.p-4 { + padding: 1.5rem !important +} + +.pt-4, .py-4 { + padding-top: 1.5rem !important +} + +.pr-4, .px-4 { + padding-right: 1.5rem !important +} + +.pb-4, .py-4 { + padding-bottom: 1.5rem !important +} + +.pl-4, .px-4 { + padding-left: 1.5rem !important +} + +.p-5 { + padding: 3rem !important +} + +.pt-5, .py-5 { + padding-top: 3rem !important +} + +.pr-5, .px-5 { + padding-right: 3rem !important +} + +.pb-5, .py-5 { + padding-bottom: 3rem !important +} + +.pl-5, .px-5 { + padding-left: 3rem !important +} + +.m-n1 { + margin: -.25rem !important +} + +.mt-n1, .my-n1 { + margin-top: -.25rem !important +} + +.mr-n1, .mx-n1 { + margin-right: -.25rem !important +} + +.mb-n1, .my-n1 { + margin-bottom: -.25rem !important +} + +.ml-n1, .mx-n1 { + margin-left: -.25rem !important +} + +.m-n2 { + margin: -.5rem !important +} + +.mt-n2, .my-n2 { + margin-top: -.5rem !important +} + +.mr-n2, .mx-n2 { + margin-right: -.5rem !important +} + +.mb-n2, .my-n2 { + margin-bottom: -.5rem !important +} + +.ml-n2, .mx-n2 { + margin-left: -.5rem !important +} + +.m-n3 { + margin: -1rem !important +} + +.mt-n3, .my-n3 { + margin-top: -1rem !important +} + +.mr-n3, .mx-n3 { + margin-right: -1rem !important +} + +.mb-n3, .my-n3 { + margin-bottom: -1rem !important +} + +.ml-n3, .mx-n3 { + margin-left: -1rem !important +} + +.m-n4 { + margin: -1.5rem !important +} + +.mt-n4, .my-n4 { + margin-top: -1.5rem !important +} + +.mr-n4, .mx-n4 { + margin-right: -1.5rem !important +} + +.mb-n4, .my-n4 { + margin-bottom: -1.5rem !important +} + +.ml-n4, .mx-n4 { + margin-left: -1.5rem !important +} + +.m-n5 { + margin: -3rem !important +} + +.mt-n5, .my-n5 { + margin-top: -3rem !important +} + +.mr-n5, .mx-n5 { + margin-right: -3rem !important +} + +.mb-n5, .my-n5 { + margin-bottom: -3rem !important +} + +.ml-n5, .mx-n5 { + margin-left: -3rem !important +} + +.m-auto { + margin: auto !important +} + +.mt-auto, .my-auto { + margin-top: auto !important +} + +.mr-auto, .mx-auto { + margin-right: auto !important +} + +.mb-auto, .my-auto { + margin-bottom: auto !important +} + +.ml-auto, .mx-auto { + margin-left: auto !important +} + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important + } + + .mt-sm-0, .my-sm-0 { + margin-top: 0 !important + } + + .mr-sm-0, .mx-sm-0 { + margin-right: 0 !important + } + + .mb-sm-0, .my-sm-0 { + margin-bottom: 0 !important + } + + .ml-sm-0, .mx-sm-0 { + margin-left: 0 !important + } + + .m-sm-1 { + margin: .25rem !important + } + + .mt-sm-1, .my-sm-1 { + margin-top: .25rem !important + } + + .mr-sm-1, .mx-sm-1 { + margin-right: .25rem !important + } + + .mb-sm-1, .my-sm-1 { + margin-bottom: .25rem !important + } + + .ml-sm-1, .mx-sm-1 { + margin-left: .25rem !important + } + + .m-sm-2 { + margin: .5rem !important + } + + .mt-sm-2, .my-sm-2 { + margin-top: .5rem !important + } + + .mr-sm-2, .mx-sm-2 { + margin-right: .5rem !important + } + + .mb-sm-2, .my-sm-2 { + margin-bottom: .5rem !important + } + + .ml-sm-2, .mx-sm-2 { + margin-left: .5rem !important + } + + .m-sm-3 { + margin: 1rem !important + } + + .mt-sm-3, .my-sm-3 { + margin-top: 1rem !important + } + + .mr-sm-3, .mx-sm-3 { + margin-right: 1rem !important + } + + .mb-sm-3, .my-sm-3 { + margin-bottom: 1rem !important + } + + .ml-sm-3, .mx-sm-3 { + margin-left: 1rem !important + } + + .m-sm-4 { + margin: 1.5rem !important + } + + .mt-sm-4, .my-sm-4 { + margin-top: 1.5rem !important + } + + .mr-sm-4, .mx-sm-4 { + margin-right: 1.5rem !important + } + + .mb-sm-4, .my-sm-4 { + margin-bottom: 1.5rem !important + } + + .ml-sm-4, .mx-sm-4 { + margin-left: 1.5rem !important + } + + .m-sm-5 { + margin: 3rem !important + } + + .mt-sm-5, .my-sm-5 { + margin-top: 3rem !important + } + + .mr-sm-5, .mx-sm-5 { + margin-right: 3rem !important + } + + .mb-sm-5, .my-sm-5 { + margin-bottom: 3rem !important + } + + .ml-sm-5, .mx-sm-5 { + margin-left: 3rem !important + } + + .p-sm-0 { + padding: 0 !important + } + + .pt-sm-0, .py-sm-0 { + padding-top: 0 !important + } + + .pr-sm-0, .px-sm-0 { + padding-right: 0 !important + } + + .pb-sm-0, .py-sm-0 { + padding-bottom: 0 !important + } + + .pl-sm-0, .px-sm-0 { + padding-left: 0 !important + } + + .p-sm-1 { + padding: .25rem !important + } + + .pt-sm-1, .py-sm-1 { + padding-top: .25rem !important + } + + .pr-sm-1, .px-sm-1 { + padding-right: .25rem !important + } + + .pb-sm-1, .py-sm-1 { + padding-bottom: .25rem !important + } + + .pl-sm-1, .px-sm-1 { + padding-left: .25rem !important + } + + .p-sm-2 { + padding: .5rem !important + } + + .pt-sm-2, .py-sm-2 { + padding-top: .5rem !important + } + + .pr-sm-2, .px-sm-2 { + padding-right: .5rem !important + } + + .pb-sm-2, .py-sm-2 { + padding-bottom: .5rem !important + } + + .pl-sm-2, .px-sm-2 { + padding-left: .5rem !important + } + + .p-sm-3 { + padding: 1rem !important + } + + .pt-sm-3, .py-sm-3 { + padding-top: 1rem !important + } + + .pr-sm-3, .px-sm-3 { + padding-right: 1rem !important + } + + .pb-sm-3, .py-sm-3 { + padding-bottom: 1rem !important + } + + .pl-sm-3, .px-sm-3 { + padding-left: 1rem !important + } + + .p-sm-4 { + padding: 1.5rem !important + } + + .pt-sm-4, .py-sm-4 { + padding-top: 1.5rem !important + } + + .pr-sm-4, .px-sm-4 { + padding-right: 1.5rem !important + } + + .pb-sm-4, .py-sm-4 { + padding-bottom: 1.5rem !important + } + + .pl-sm-4, .px-sm-4 { + padding-left: 1.5rem !important + } + + .p-sm-5 { + padding: 3rem !important + } + + .pt-sm-5, .py-sm-5 { + padding-top: 3rem !important + } + + .pr-sm-5, .px-sm-5 { + padding-right: 3rem !important + } + + .pb-sm-5, .py-sm-5 { + padding-bottom: 3rem !important + } + + .pl-sm-5, .px-sm-5 { + padding-left: 3rem !important + } + + .m-sm-n1 { + margin: -.25rem !important + } + + .mt-sm-n1, .my-sm-n1 { + margin-top: -.25rem !important + } + + .mr-sm-n1, .mx-sm-n1 { + margin-right: -.25rem !important + } + + .mb-sm-n1, .my-sm-n1 { + margin-bottom: -.25rem !important + } + + .ml-sm-n1, .mx-sm-n1 { + margin-left: -.25rem !important + } + + .m-sm-n2 { + margin: -.5rem !important + } + + .mt-sm-n2, .my-sm-n2 { + margin-top: -.5rem !important + } + + .mr-sm-n2, .mx-sm-n2 { + margin-right: -.5rem !important + } + + .mb-sm-n2, .my-sm-n2 { + margin-bottom: -.5rem !important + } + + .ml-sm-n2, .mx-sm-n2 { + margin-left: -.5rem !important + } + + .m-sm-n3 { + margin: -1rem !important + } + + .mt-sm-n3, .my-sm-n3 { + margin-top: -1rem !important + } + + .mr-sm-n3, .mx-sm-n3 { + margin-right: -1rem !important + } + + .mb-sm-n3, .my-sm-n3 { + margin-bottom: -1rem !important + } + + .ml-sm-n3, .mx-sm-n3 { + margin-left: -1rem !important + } + + .m-sm-n4 { + margin: -1.5rem !important + } + + .mt-sm-n4, .my-sm-n4 { + margin-top: -1.5rem !important + } + + .mr-sm-n4, .mx-sm-n4 { + margin-right: -1.5rem !important + } + + .mb-sm-n4, .my-sm-n4 { + margin-bottom: -1.5rem !important + } + + .ml-sm-n4, .mx-sm-n4 { + margin-left: -1.5rem !important + } + + .m-sm-n5 { + margin: -3rem !important + } + + .mt-sm-n5, .my-sm-n5 { + margin-top: -3rem !important + } + + .mr-sm-n5, .mx-sm-n5 { + margin-right: -3rem !important + } + + .mb-sm-n5, .my-sm-n5 { + margin-bottom: -3rem !important + } + + .ml-sm-n5, .mx-sm-n5 { + margin-left: -3rem !important + } + + .m-sm-auto { + margin: auto !important + } + + .mt-sm-auto, .my-sm-auto { + margin-top: auto !important + } + + .mr-sm-auto, .mx-sm-auto { + margin-right: auto !important + } + + .mb-sm-auto, .my-sm-auto { + margin-bottom: auto !important + } + + .ml-sm-auto, .mx-sm-auto { + margin-left: auto !important + } +} + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important + } + + .mt-md-0, .my-md-0 { + margin-top: 0 !important + } + + .mr-md-0, .mx-md-0 { + margin-right: 0 !important + } + + .mb-md-0, .my-md-0 { + margin-bottom: 0 !important + } + + .ml-md-0, .mx-md-0 { + margin-left: 0 !important + } + + .m-md-1 { + margin: .25rem !important + } + + .mt-md-1, .my-md-1 { + margin-top: .25rem !important + } + + .mr-md-1, .mx-md-1 { + margin-right: .25rem !important + } + + .mb-md-1, .my-md-1 { + margin-bottom: .25rem !important + } + + .ml-md-1, .mx-md-1 { + margin-left: .25rem !important + } + + .m-md-2 { + margin: .5rem !important + } + + .mt-md-2, .my-md-2 { + margin-top: .5rem !important + } + + .mr-md-2, .mx-md-2 { + margin-right: .5rem !important + } + + .mb-md-2, .my-md-2 { + margin-bottom: .5rem !important + } + + .ml-md-2, .mx-md-2 { + margin-left: .5rem !important + } + + .m-md-3 { + margin: 1rem !important + } + + .mt-md-3, .my-md-3 { + margin-top: 1rem !important + } + + .mr-md-3, .mx-md-3 { + margin-right: 1rem !important + } + + .mb-md-3, .my-md-3 { + margin-bottom: 1rem !important + } + + .ml-md-3, .mx-md-3 { + margin-left: 1rem !important + } + + .m-md-4 { + margin: 1.5rem !important + } + + .mt-md-4, .my-md-4 { + margin-top: 1.5rem !important + } + + .mr-md-4, .mx-md-4 { + margin-right: 1.5rem !important + } + + .mb-md-4, .my-md-4 { + margin-bottom: 1.5rem !important + } + + .ml-md-4, .mx-md-4 { + margin-left: 1.5rem !important + } + + .m-md-5 { + margin: 3rem !important + } + + .mt-md-5, .my-md-5 { + margin-top: 3rem !important + } + + .mr-md-5, .mx-md-5 { + margin-right: 3rem !important + } + + .mb-md-5, .my-md-5 { + margin-bottom: 3rem !important + } + + .ml-md-5, .mx-md-5 { + margin-left: 3rem !important + } + + .p-md-0 { + padding: 0 !important + } + + .pt-md-0, .py-md-0 { + padding-top: 0 !important + } + + .pr-md-0, .px-md-0 { + padding-right: 0 !important + } + + .pb-md-0, .py-md-0 { + padding-bottom: 0 !important + } + + .pl-md-0, .px-md-0 { + padding-left: 0 !important + } + + .p-md-1 { + padding: .25rem !important + } + + .pt-md-1, .py-md-1 { + padding-top: .25rem !important + } + + .pr-md-1, .px-md-1 { + padding-right: .25rem !important + } + + .pb-md-1, .py-md-1 { + padding-bottom: .25rem !important + } + + .pl-md-1, .px-md-1 { + padding-left: .25rem !important + } + + .p-md-2 { + padding: .5rem !important + } + + .pt-md-2, .py-md-2 { + padding-top: .5rem !important + } + + .pr-md-2, .px-md-2 { + padding-right: .5rem !important + } + + .pb-md-2, .py-md-2 { + padding-bottom: .5rem !important + } + + .pl-md-2, .px-md-2 { + padding-left: .5rem !important + } + + .p-md-3 { + padding: 1rem !important + } + + .pt-md-3, .py-md-3 { + padding-top: 1rem !important + } + + .pr-md-3, .px-md-3 { + padding-right: 1rem !important + } + + .pb-md-3, .py-md-3 { + padding-bottom: 1rem !important + } + + .pl-md-3, .px-md-3 { + padding-left: 1rem !important + } + + .p-md-4 { + padding: 1.5rem !important + } + + .pt-md-4, .py-md-4 { + padding-top: 1.5rem !important + } + + .pr-md-4, .px-md-4 { + padding-right: 1.5rem !important + } + + .pb-md-4, .py-md-4 { + padding-bottom: 1.5rem !important + } + + .pl-md-4, .px-md-4 { + padding-left: 1.5rem !important + } + + .p-md-5 { + padding: 3rem !important + } + + .pt-md-5, .py-md-5 { + padding-top: 3rem !important + } + + .pr-md-5, .px-md-5 { + padding-right: 3rem !important + } + + .pb-md-5, .py-md-5 { + padding-bottom: 3rem !important + } + + .pl-md-5, .px-md-5 { + padding-left: 3rem !important + } + + .m-md-n1 { + margin: -.25rem !important + } + + .mt-md-n1, .my-md-n1 { + margin-top: -.25rem !important + } + + .mr-md-n1, .mx-md-n1 { + margin-right: -.25rem !important + } + + .mb-md-n1, .my-md-n1 { + margin-bottom: -.25rem !important + } + + .ml-md-n1, .mx-md-n1 { + margin-left: -.25rem !important + } + + .m-md-n2 { + margin: -.5rem !important + } + + .mt-md-n2, .my-md-n2 { + margin-top: -.5rem !important + } + + .mr-md-n2, .mx-md-n2 { + margin-right: -.5rem !important + } + + .mb-md-n2, .my-md-n2 { + margin-bottom: -.5rem !important + } + + .ml-md-n2, .mx-md-n2 { + margin-left: -.5rem !important + } + + .m-md-n3 { + margin: -1rem !important + } + + .mt-md-n3, .my-md-n3 { + margin-top: -1rem !important + } + + .mr-md-n3, .mx-md-n3 { + margin-right: -1rem !important + } + + .mb-md-n3, .my-md-n3 { + margin-bottom: -1rem !important + } + + .ml-md-n3, .mx-md-n3 { + margin-left: -1rem !important + } + + .m-md-n4 { + margin: -1.5rem !important + } + + .mt-md-n4, .my-md-n4 { + margin-top: -1.5rem !important + } + + .mr-md-n4, .mx-md-n4 { + margin-right: -1.5rem !important + } + + .mb-md-n4, .my-md-n4 { + margin-bottom: -1.5rem !important + } + + .ml-md-n4, .mx-md-n4 { + margin-left: -1.5rem !important + } + + .m-md-n5 { + margin: -3rem !important + } + + .mt-md-n5, .my-md-n5 { + margin-top: -3rem !important + } + + .mr-md-n5, .mx-md-n5 { + margin-right: -3rem !important + } + + .mb-md-n5, .my-md-n5 { + margin-bottom: -3rem !important + } + + .ml-md-n5, .mx-md-n5 { + margin-left: -3rem !important + } + + .m-md-auto { + margin: auto !important + } + + .mt-md-auto, .my-md-auto { + margin-top: auto !important + } + + .mr-md-auto, .mx-md-auto { + margin-right: auto !important + } + + .mb-md-auto, .my-md-auto { + margin-bottom: auto !important + } + + .ml-md-auto, .mx-md-auto { + margin-left: auto !important + } +} + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important + } + + .mt-lg-0, .my-lg-0 { + margin-top: 0 !important + } + + .mr-lg-0, .mx-lg-0 { + margin-right: 0 !important + } + + .mb-lg-0, .my-lg-0 { + margin-bottom: 0 !important + } + + .ml-lg-0, .mx-lg-0 { + margin-left: 0 !important + } + + .m-lg-1 { + margin: .25rem !important + } + + .mt-lg-1, .my-lg-1 { + margin-top: .25rem !important + } + + .mr-lg-1, .mx-lg-1 { + margin-right: .25rem !important + } + + .mb-lg-1, .my-lg-1 { + margin-bottom: .25rem !important + } + + .ml-lg-1, .mx-lg-1 { + margin-left: .25rem !important + } + + .m-lg-2 { + margin: .5rem !important + } + + .mt-lg-2, .my-lg-2 { + margin-top: .5rem !important + } + + .mr-lg-2, .mx-lg-2 { + margin-right: .5rem !important + } + + .mb-lg-2, .my-lg-2 { + margin-bottom: .5rem !important + } + + .ml-lg-2, .mx-lg-2 { + margin-left: .5rem !important + } + + .m-lg-3 { + margin: 1rem !important + } + + .mt-lg-3, .my-lg-3 { + margin-top: 1rem !important + } + + .mr-lg-3, .mx-lg-3 { + margin-right: 1rem !important + } + + .mb-lg-3, .my-lg-3 { + margin-bottom: 1rem !important + } + + .ml-lg-3, .mx-lg-3 { + margin-left: 1rem !important + } + + .m-lg-4 { + margin: 1.5rem !important + } + + .mt-lg-4, .my-lg-4 { + margin-top: 1.5rem !important + } + + .mr-lg-4, .mx-lg-4 { + margin-right: 1.5rem !important + } + + .mb-lg-4, .my-lg-4 { + margin-bottom: 1.5rem !important + } + + .ml-lg-4, .mx-lg-4 { + margin-left: 1.5rem !important + } + + .m-lg-5 { + margin: 3rem !important + } + + .mt-lg-5, .my-lg-5 { + margin-top: 3rem !important + } + + .mr-lg-5, .mx-lg-5 { + margin-right: 3rem !important + } + + .mb-lg-5, .my-lg-5 { + margin-bottom: 3rem !important + } + + .ml-lg-5, .mx-lg-5 { + margin-left: 3rem !important + } + + .p-lg-0 { + padding: 0 !important + } + + .pt-lg-0, .py-lg-0 { + padding-top: 0 !important + } + + .pr-lg-0, .px-lg-0 { + padding-right: 0 !important + } + + .pb-lg-0, .py-lg-0 { + padding-bottom: 0 !important + } + + .pl-lg-0, .px-lg-0 { + padding-left: 0 !important + } + + .p-lg-1 { + padding: .25rem !important + } + + .pt-lg-1, .py-lg-1 { + padding-top: .25rem !important + } + + .pr-lg-1, .px-lg-1 { + padding-right: .25rem !important + } + + .pb-lg-1, .py-lg-1 { + padding-bottom: .25rem !important + } + + .pl-lg-1, .px-lg-1 { + padding-left: .25rem !important + } + + .p-lg-2 { + padding: .5rem !important + } + + .pt-lg-2, .py-lg-2 { + padding-top: .5rem !important + } + + .pr-lg-2, .px-lg-2 { + padding-right: .5rem !important + } + + .pb-lg-2, .py-lg-2 { + padding-bottom: .5rem !important + } + + .pl-lg-2, .px-lg-2 { + padding-left: .5rem !important + } + + .p-lg-3 { + padding: 1rem !important + } + + .pt-lg-3, .py-lg-3 { + padding-top: 1rem !important + } + + .pr-lg-3, .px-lg-3 { + padding-right: 1rem !important + } + + .pb-lg-3, .py-lg-3 { + padding-bottom: 1rem !important + } + + .pl-lg-3, .px-lg-3 { + padding-left: 1rem !important + } + + .p-lg-4 { + padding: 1.5rem !important + } + + .pt-lg-4, .py-lg-4 { + padding-top: 1.5rem !important + } + + .pr-lg-4, .px-lg-4 { + padding-right: 1.5rem !important + } + + .pb-lg-4, .py-lg-4 { + padding-bottom: 1.5rem !important + } + + .pl-lg-4, .px-lg-4 { + padding-left: 1.5rem !important + } + + .p-lg-5 { + padding: 3rem !important + } + + .pt-lg-5, .py-lg-5 { + padding-top: 3rem !important + } + + .pr-lg-5, .px-lg-5 { + padding-right: 3rem !important + } + + .pb-lg-5, .py-lg-5 { + padding-bottom: 3rem !important + } + + .pl-lg-5, .px-lg-5 { + padding-left: 3rem !important + } + + .m-lg-n1 { + margin: -.25rem !important + } + + .mt-lg-n1, .my-lg-n1 { + margin-top: -.25rem !important + } + + .mr-lg-n1, .mx-lg-n1 { + margin-right: -.25rem !important + } + + .mb-lg-n1, .my-lg-n1 { + margin-bottom: -.25rem !important + } + + .ml-lg-n1, .mx-lg-n1 { + margin-left: -.25rem !important + } + + .m-lg-n2 { + margin: -.5rem !important + } + + .mt-lg-n2, .my-lg-n2 { + margin-top: -.5rem !important + } + + .mr-lg-n2, .mx-lg-n2 { + margin-right: -.5rem !important + } + + .mb-lg-n2, .my-lg-n2 { + margin-bottom: -.5rem !important + } + + .ml-lg-n2, .mx-lg-n2 { + margin-left: -.5rem !important + } + + .m-lg-n3 { + margin: -1rem !important + } + + .mt-lg-n3, .my-lg-n3 { + margin-top: -1rem !important + } + + .mr-lg-n3, .mx-lg-n3 { + margin-right: -1rem !important + } + + .mb-lg-n3, .my-lg-n3 { + margin-bottom: -1rem !important + } + + .ml-lg-n3, .mx-lg-n3 { + margin-left: -1rem !important + } + + .m-lg-n4 { + margin: -1.5rem !important + } + + .mt-lg-n4, .my-lg-n4 { + margin-top: -1.5rem !important + } + + .mr-lg-n4, .mx-lg-n4 { + margin-right: -1.5rem !important + } + + .mb-lg-n4, .my-lg-n4 { + margin-bottom: -1.5rem !important + } + + .ml-lg-n4, .mx-lg-n4 { + margin-left: -1.5rem !important + } + + .m-lg-n5 { + margin: -3rem !important + } + + .mt-lg-n5, .my-lg-n5 { + margin-top: -3rem !important + } + + .mr-lg-n5, .mx-lg-n5 { + margin-right: -3rem !important + } + + .mb-lg-n5, .my-lg-n5 { + margin-bottom: -3rem !important + } + + .ml-lg-n5, .mx-lg-n5 { + margin-left: -3rem !important + } + + .m-lg-auto { + margin: auto !important + } + + .mt-lg-auto, .my-lg-auto { + margin-top: auto !important + } + + .mr-lg-auto, .mx-lg-auto { + margin-right: auto !important + } + + .mb-lg-auto, .my-lg-auto { + margin-bottom: auto !important + } + + .ml-lg-auto, .mx-lg-auto { + margin-left: auto !important + } +} + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important + } + + .mt-xl-0, .my-xl-0 { + margin-top: 0 !important + } + + .mr-xl-0, .mx-xl-0 { + margin-right: 0 !important + } + + .mb-xl-0, .my-xl-0 { + margin-bottom: 0 !important + } + + .ml-xl-0, .mx-xl-0 { + margin-left: 0 !important + } + + .m-xl-1 { + margin: .25rem !important + } + + .mt-xl-1, .my-xl-1 { + margin-top: .25rem !important + } + + .mr-xl-1, .mx-xl-1 { + margin-right: .25rem !important + } + + .mb-xl-1, .my-xl-1 { + margin-bottom: .25rem !important + } + + .ml-xl-1, .mx-xl-1 { + margin-left: .25rem !important + } + + .m-xl-2 { + margin: .5rem !important + } + + .mt-xl-2, .my-xl-2 { + margin-top: .5rem !important + } + + .mr-xl-2, .mx-xl-2 { + margin-right: .5rem !important + } + + .mb-xl-2, .my-xl-2 { + margin-bottom: .5rem !important + } + + .ml-xl-2, .mx-xl-2 { + margin-left: .5rem !important + } + + .m-xl-3 { + margin: 1rem !important + } + + .mt-xl-3, .my-xl-3 { + margin-top: 1rem !important + } + + .mr-xl-3, .mx-xl-3 { + margin-right: 1rem !important + } + + .mb-xl-3, .my-xl-3 { + margin-bottom: 1rem !important + } + + .ml-xl-3, .mx-xl-3 { + margin-left: 1rem !important + } + + .m-xl-4 { + margin: 1.5rem !important + } + + .mt-xl-4, .my-xl-4 { + margin-top: 1.5rem !important + } + + .mr-xl-4, .mx-xl-4 { + margin-right: 1.5rem !important + } + + .mb-xl-4, .my-xl-4 { + margin-bottom: 1.5rem !important + } + + .ml-xl-4, .mx-xl-4 { + margin-left: 1.5rem !important + } + + .m-xl-5 { + margin: 3rem !important + } + + .mt-xl-5, .my-xl-5 { + margin-top: 3rem !important + } + + .mr-xl-5, .mx-xl-5 { + margin-right: 3rem !important + } + + .mb-xl-5, .my-xl-5 { + margin-bottom: 3rem !important + } + + .ml-xl-5, .mx-xl-5 { + margin-left: 3rem !important + } + + .p-xl-0 { + padding: 0 !important + } + + .pt-xl-0, .py-xl-0 { + padding-top: 0 !important + } + + .pr-xl-0, .px-xl-0 { + padding-right: 0 !important + } + + .pb-xl-0, .py-xl-0 { + padding-bottom: 0 !important + } + + .pl-xl-0, .px-xl-0 { + padding-left: 0 !important + } + + .p-xl-1 { + padding: .25rem !important + } + + .pt-xl-1, .py-xl-1 { + padding-top: .25rem !important + } + + .pr-xl-1, .px-xl-1 { + padding-right: .25rem !important + } + + .pb-xl-1, .py-xl-1 { + padding-bottom: .25rem !important + } + + .pl-xl-1, .px-xl-1 { + padding-left: .25rem !important + } + + .p-xl-2 { + padding: .5rem !important + } + + .pt-xl-2, .py-xl-2 { + padding-top: .5rem !important + } + + .pr-xl-2, .px-xl-2 { + padding-right: .5rem !important + } + + .pb-xl-2, .py-xl-2 { + padding-bottom: .5rem !important + } + + .pl-xl-2, .px-xl-2 { + padding-left: .5rem !important + } + + .p-xl-3 { + padding: 1rem !important + } + + .pt-xl-3, .py-xl-3 { + padding-top: 1rem !important + } + + .pr-xl-3, .px-xl-3 { + padding-right: 1rem !important + } + + .pb-xl-3, .py-xl-3 { + padding-bottom: 1rem !important + } + + .pl-xl-3, .px-xl-3 { + padding-left: 1rem !important + } + + .p-xl-4 { + padding: 1.5rem !important + } + + .pt-xl-4, .py-xl-4 { + padding-top: 1.5rem !important + } + + .pr-xl-4, .px-xl-4 { + padding-right: 1.5rem !important + } + + .pb-xl-4, .py-xl-4 { + padding-bottom: 1.5rem !important + } + + .pl-xl-4, .px-xl-4 { + padding-left: 1.5rem !important + } + + .p-xl-5 { + padding: 3rem !important + } + + .pt-xl-5, .py-xl-5 { + padding-top: 3rem !important + } + + .pr-xl-5, .px-xl-5 { + padding-right: 3rem !important + } + + .pb-xl-5, .py-xl-5 { + padding-bottom: 3rem !important + } + + .pl-xl-5, .px-xl-5 { + padding-left: 3rem !important + } + + .m-xl-n1 { + margin: -.25rem !important + } + + .mt-xl-n1, .my-xl-n1 { + margin-top: -.25rem !important + } + + .mr-xl-n1, .mx-xl-n1 { + margin-right: -.25rem !important + } + + .mb-xl-n1, .my-xl-n1 { + margin-bottom: -.25rem !important + } + + .ml-xl-n1, .mx-xl-n1 { + margin-left: -.25rem !important + } + + .m-xl-n2 { + margin: -.5rem !important + } + + .mt-xl-n2, .my-xl-n2 { + margin-top: -.5rem !important + } + + .mr-xl-n2, .mx-xl-n2 { + margin-right: -.5rem !important + } + + .mb-xl-n2, .my-xl-n2 { + margin-bottom: -.5rem !important + } + + .ml-xl-n2, .mx-xl-n2 { + margin-left: -.5rem !important + } + + .m-xl-n3 { + margin: -1rem !important + } + + .mt-xl-n3, .my-xl-n3 { + margin-top: -1rem !important + } + + .mr-xl-n3, .mx-xl-n3 { + margin-right: -1rem !important + } + + .mb-xl-n3, .my-xl-n3 { + margin-bottom: -1rem !important + } + + .ml-xl-n3, .mx-xl-n3 { + margin-left: -1rem !important + } + + .m-xl-n4 { + margin: -1.5rem !important + } + + .mt-xl-n4, .my-xl-n4 { + margin-top: -1.5rem !important + } + + .mr-xl-n4, .mx-xl-n4 { + margin-right: -1.5rem !important + } + + .mb-xl-n4, .my-xl-n4 { + margin-bottom: -1.5rem !important + } + + .ml-xl-n4, .mx-xl-n4 { + margin-left: -1.5rem !important + } + + .m-xl-n5 { + margin: -3rem !important + } + + .mt-xl-n5, .my-xl-n5 { + margin-top: -3rem !important + } + + .mr-xl-n5, .mx-xl-n5 { + margin-right: -3rem !important + } + + .mb-xl-n5, .my-xl-n5 { + margin-bottom: -3rem !important + } + + .ml-xl-n5, .mx-xl-n5 { + margin-left: -3rem !important + } + + .m-xl-auto { + margin: auto !important + } + + .mt-xl-auto, .my-xl-auto { + margin-top: auto !important + } + + .mr-xl-auto, .mx-xl-auto { + margin-right: auto !important + } + + .mb-xl-auto, .my-xl-auto { + margin-bottom: auto !important + } + + .ml-xl-auto, .mx-xl-auto { + margin-left: auto !important + } +} + +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + pointer-events: auto; + content: ""; + background-color: rgba(0, 0, 0, 0) +} + +.text-monospace { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important +} + +.text-justify { + text-align: justify !important +} + +.text-wrap { + white-space: normal !important +} + +.text-nowrap { + white-space: nowrap !important +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.text-left { + text-align: left !important +} + +.text-right { + text-align: right !important +} + +.text-center { + text-align: center !important +} + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important + } + + .text-sm-right { + text-align: right !important + } + + .text-sm-center { + text-align: center !important + } +} + +@media (min-width: 768px) { + .text-md-left { + text-align: left !important + } + + .text-md-right { + text-align: right !important + } + + .text-md-center { + text-align: center !important + } +} + +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important + } + + .text-lg-right { + text-align: right !important + } + + .text-lg-center { + text-align: center !important + } +} + +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important + } + + .text-xl-right { + text-align: right !important + } + + .text-xl-center { + text-align: center !important + } +} + +.text-lowercase { + text-transform: lowercase !important +} + +.text-uppercase { + text-transform: uppercase !important +} + +.text-capitalize { + text-transform: capitalize !important +} + +.font-weight-light { + font-weight: 300 !important +} + +.font-weight-lighter { + font-weight: lighter !important +} + +.font-weight-normal { + font-weight: 400 !important +} + +.font-weight-bold { + font-weight: 700 !important +} + +.font-weight-bolder { + font-weight: bolder !important +} + +.font-italic { + font-style: italic !important +} + +.text-white { + color: #fff !important +} + +.text-primary { + color: #007bff !important +} + +a.text-primary:focus, a.text-primary:hover { + color: #0056b3 !important +} + +.text-secondary { + color: #6c757d !important +} + +a.text-secondary:focus, a.text-secondary:hover { + color: #494f54 !important +} + +.text-success { + color: #28a745 !important +} + +a.text-success:focus, a.text-success:hover { + color: #19692c !important +} + +.text-info { + color: #17a2b8 !important +} + +a.text-info:focus, a.text-info:hover { + color: #0f6674 !important +} + +.text-warning { + color: #ffc107 !important +} + +a.text-warning:focus, a.text-warning:hover { + color: #ba8b00 !important +} + +.text-danger { + color: #dc3545 !important +} + +a.text-danger:focus, a.text-danger:hover { + color: #a71d2a !important +} + +.text-light { + color: #f8f9fa !important +} + +a.text-light:focus, a.text-light:hover { + color: #cbd3da !important +} + +.text-dark { + color: #343a40 !important +} + +a.text-dark:focus, a.text-dark:hover { + color: #121416 !important +} + +.text-body { + color: #212529 !important +} + +.text-muted { + color: #6c757d !important +} + +.text-black-50 { + color: rgba(0, 0, 0, .5) !important +} + +.text-white-50 { + color: rgba(255, 255, 255, .5) !important +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0 +} + +.text-decoration-none { + text-decoration: none !important +} + +.text-break { + word-break: break-word !important; + overflow-wrap: break-word !important +} + +.text-reset { + color: inherit !important +} + +.visible { + visibility: visible !important +} + +.invisible { + visibility: hidden !important +} + +@media print { + *, ::after, ::before { + text-shadow: none !important; + box-shadow: none !important + } + + a:not(.btn) { + text-decoration: underline + } + + abbr[title]::after { + content: " (" attr(title) ")" + } + + pre { + white-space: pre-wrap !important + } + + blockquote, pre { + border: 1px solid #adb5bd; + page-break-inside: avoid + } + + thead { + display: table-header-group + } + + img, tr { + page-break-inside: avoid + } + + h2, h3, p { + orphans: 3; + widows: 3 + } + + h2, h3 { + page-break-after: avoid + } + + @page { + size: a3 + } + + body { + min-width: 992px !important + } + + .container { + min-width: 992px !important + } + + .navbar { + display: none + } + + .badge { + border: 1px solid #000 + } + + .table { + border-collapse: collapse !important + } + + .table td, .table th { + background-color: #fff !important + } + + .table-bordered td, .table-bordered th { + border: 1px solid #dee2e6 !important + } + + .table-dark { + color: inherit + } + + .table-dark tbody + tbody, .table-dark td, .table-dark th, .table-dark thead th { + border-color: #dee2e6 + } + + .table .thead-dark th { + color: inherit; + border-color: #dee2e6 + } +} + +/*# sourceMappingURL=bootstrap.min.css.map */ + diff --git a/static/css/fonts.css b/static/css/fonts.css new file mode 100644 index 0000000..6598fa3 --- /dev/null +++ b/static/css/fonts.css @@ -0,0 +1,384 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'), url(https://fonts.gstatic.com/s/sourcecodepro/v13/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjDvucq7Gq0DDzS.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'), url(https://fonts.gstatic.com/s/sourcecodepro/v13/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjDt-cq7Gq0DDzS.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'), url(https://fonts.gstatic.com/s/sourcecodepro/v13/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjDsOcq7Gq0DDzS.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'), url(https://fonts.gstatic.com/s/sourcecodepro/v13/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjDvOcq7Gq0DDzS.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'), url(https://fonts.gstatic.com/s/sourcecodepro/v13/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjDvecq7Gq0DDzS.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'), url(https://fonts.gstatic.com/s/sourcecodepro/v13/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjDs-cq7Gq0DA.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 400; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7qsDJB9cme_xc.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 400; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7jsDJB9cme_xc.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 400; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7rsDJB9cme_xc.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 400; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7ksDJB9cme_xc.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 400; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7osDJB9cme_xc.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 400; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7psDJB9cme_xc.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 400; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK1dSBYKcSV-LCoeQqfX1RYOo3qPZ7nsDJB9cme.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 600; + src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCdh18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 600; + src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCdo18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 600; + src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCdg18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 600; + src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCdv18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 600; + src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCdj18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 600; + src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCdi18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 600; + src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZY4lCds18S0xR41.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 700; + src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZclSdh18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 700; + src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZclSdo18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 700; + src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZclSdg18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 700; + src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZclSdv18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 700; + src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZclSdj18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 700; + src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZclSdi18S0xR41YDw.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 700; + src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKwdSBYKcSV-LCoeQqfX1RYOo3qPZZclSds18S0xR41.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK3dSBYKcSV-LCoeQqfX1RYOo3qNa7lujVj9_mf.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK3dSBYKcSV-LCoeQqfX1RYOo3qPK7lujVj9_mf.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK3dSBYKcSV-LCoeQqfX1RYOo3qNK7lujVj9_mf.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK3dSBYKcSV-LCoeQqfX1RYOo3qO67lujVj9_mf.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK3dSBYKcSV-LCoeQqfX1RYOo3qN67lujVj9_mf.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK3dSBYKcSV-LCoeQqfX1RYOo3qNq7lujVj9_mf.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7lujVj9w.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmhdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwkxdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmxdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlBdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmBdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwmRdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i54rwlxdu3cOWxw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmhdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwkxdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmxdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRdu3cOWxy40.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu3cOWxw.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css index a4b53dd..73bf97f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -4,14 +4,28 @@ body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; color: #444; } +/* + * Formatting the header area + */ + + +/*a:link { + color: #3c6aa7; + text-decoration: none; + font-weight: normal +} +*/ a:visited { color: purple; } + a:hover { color: #26446b; text-decoration: underline; font-weight: none } + + header { background-color: #050053; height: 35px; @@ -29,9 +43,12 @@ header h1.logo:hover { color: #fff; text-decoration: none; } +/* + * Centering the body content + */ .container { width: 900px; - margin: 0 auto; + margin: 0; } div.home { padding: 10px 0 30px 0; @@ -54,6 +71,7 @@ div.success { -moz-border-radius: 6px; border-radius: 6px; } + h2 { font-size: 3em; margin-top: 40px; @@ -68,12 +86,18 @@ h3 { letter-spacing: -1px; color: #999; } + +h1-center { + text-align: center; +} + .div1 { width: 300px; height: 100px; border: 1px solid blue; box-sizing: border-box; } + .div2 { width: 300px; height: 100px; @@ -81,9 +105,11 @@ h3 { border: 1px solid red; box-sizing: border-box; } + * { box-sizing: border-box; } + .menu { float: left; margin-top: 8px; @@ -104,13 +130,14 @@ h3 { .nav ul { list-style-type: none; padding: 0; -} +} + .nav ul a { color: #000; text-decoration: none; } .button-holder{ - padding-top:100px; + padding-top:20px; } .ajax-button{ position:relative; @@ -161,6 +188,16 @@ h3 { animation-iteration-count: infinite; -webkit-animation-iteration-count: infinite; } + +@keyframes rotateAnimation { + 0% {transform: rotate(0deg);} + 100% {transform: rotate(360deg);} +} +@-webkit-keyframes wk-rotateAnimation { + 0% {-webkit-transform: rotate(0deg);} + 100% {-webkit-transform: rotate(360deg);} +} + .fa{ color:#3c6aa7; font-size:18px !important; @@ -184,90 +221,80 @@ h3 { -webkit-transform:scale(0) !important; transform:scale(0) !important; } -@keyframes rotateAnimation { - 0% {transform: rotate(0deg);} - 100% {transform: rotate(360deg);} + +.rules-list { + margin: 2rem; } -@-webkit-keyframes wk-rotateAnimation { - 0% {-webkit-transform: rotate(0deg);} - 100% {-webkit-transform: rotate(360deg);} + +@-webkit-keyframes sk-circleFadeDelay { + 0%, 39%, 100% { opacity: 0; } + 40% { opacity: 1; } } -.form-container { - padding: 25px; +@keyframes sk-circleFadeDelay { + 0%, 39%, 100% { opacity: 0; } + 40% { opacity: 1; } } -form.account-form > * { - margin-top: 5px; - margin-bottom: 5px; +.rule-row { + border: 1px solid #000; + margin: 10px 0; + padding: 10px 30px 5px 30px; } -form.account-form h1 { +#outer +{ + width:100%; text-align: center; } - -form.account-form button { - float: right; +.inner +{ + display: inline-block; } - -form.account-form select { - padding: 0; +.hippocrates { + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; } -form.account-form { - margin-top: auto; - margin-left: auto; - margin-right: auto; - max-width: 450px; - background-color: #c9c9c9; - border: 1px solid gray; - border-radius: 5px; - padding: 10px; +.hippocrates td, .hippocrates th { + border: 1px solid #ddd; + padding: 8px; } -.error-msg { - color: #b30000; - text-align: center; -} +.hippocrates tr:nth-child(even){background-color: #f2f2f2;} -.diagnosis { - padding: 400px; - color: #2b6ca8; - display: block; - margin-top: 1em; - margin-bottom: 1em; - margin-left: 40px; - margin-right: 40px; - background-color: pink; +.hippocrates tr:hover {background-color: #ddd;} + +.hippocrates th { + padding-top: 12px; + padding-bottom: 12px; + text-align: left; + background-color: #26446b; + color: white; } -.success-wrap { - overflow: hidden; - border-left:1em solid transparent; - border-right:1em solid transparent; - text-overflow: ellipsis; +.rules-test { + padding-left: 50; + padding-right: 50; + margin-right: 50px; + margin-left: 50px; + color: #444444; } -.success-wrap:hover { - overflow: visible; -} -.success-form { - margin-top: auto; - margin-left: auto; - margin-right: auto; - max-width: 1000px; - background-color: #c9c9c9; - border: 1px solid gray; - border-radius: 5px; - padding: 10px; +n { + color: #0066aa; } -.button-align { - align-items: right; +.alert-soft { + padding-left: 25px; + padding-right: 25px; + margin-right: 25px; + margin-left: 25px; } -.test-banner { - background-color: #ececec; +.btn-padded { + margin-top: 5px; } \ No newline at end of file diff --git a/static/css/nav.css b/static/css/nav.css new file mode 100644 index 0000000..8c88fbe --- /dev/null +++ b/static/css/nav.css @@ -0,0 +1,81 @@ + +.navbar { + z-index: 9999; + border-radius: 0px; + padding-bottom: 10px; +} + +.navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { + border-color: #02377c; + + color: white; +} + +.navbar-inverse { + background-color: #6b7078; + border-color: #0067a3; +} + +.navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #024bbf; +} + +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-nav > li > a { + color: #fff; +} + +.navbar-inverse .navbar-brand:hover { + text-decoration: underline; +} + +.navbar-inverse .navbar-toggle { + border-color: #eee; +} + +.navbar { + margin-bottom: 0px; +} + + +.navbar-brand img { + width: 65px; + margin-left: 60px; +} + +.navbar-inverse .navbar-toggle { + margin-top: 20px; +} + +.navbar-nav li { + margin-top: 20px; +} + +#last_nav_link { + margin-right: 45px; +} + +nav { + padding: 10px; + font-size: 17px; + color: white; + height: 85px; + background-color: #6b7078; +} + +nav > a { + color: white; + margin-right: 10px; +} + +.nav-expand-line { + height: 2px; + margin: 6px; + background-color: white; +} + +#navbarSupportedContent { + background-color: #6b7078; + padding: 10px; +} \ No newline at end of file diff --git a/static/css/site.css b/static/css/site.css new file mode 100644 index 0000000..da42056 --- /dev/null +++ b/static/css/site.css @@ -0,0 +1,201 @@ +aaay { + color: #222; + background: #aaa; + margin: 0px; + padding: 0px; + font-family: Source Sans Pro, Helvetica, Arial, sans-serif; + font-size: 17px; + background-color: #006dad; +} + +.title { + font-weight: bold; +} + +.main_content { + margin: 0px; + padding: 0px; + background-color: #fff; + padding-bottom: 50px; +} + +nav { + background-color: #0073b7; + padding: 10px; + font-size: 20px; + color: white; + height: 65px; +} + +nav > a { + color: white; + margin-right: 10px; +} + +.hero { + background-color: #0073b7; + color: white; + font-size: 14px; + padding: 50px 0px; + text-align: center; +} + +.hero h1 { + font-size: 24px; + margin-bottom: 30px; +} + +.hero a { + color: white; + text-decoration: underline; +} + +.hero-inner { + max-width: 500px; + margin-right: auto; + margin-left: auto; + margin-bottom: 30px; +} + +.test-links { + background-color: #ececec; + padding: 10px 0px; + text-align: center; + border-bottom: 1px solid #d3d3d3; + border-top: 1px solid #d3d3d3; + margin-bottom: 0px; +} + +.test-links .stat { + display: inline-block; + min-width: 170px; + padding: 2px; + font-size: 16px; + color: #003d61; + +} + +.test-links .stat a { + color: #003d61; +} + +.project-list .subtitle { + font-style: italic; + +} + +.project-list h2 { + font-size: 24px; + font-weight: bold; +} + +.project-list .project { + border: 1px solid #d3d3d3; + padding: 10px; + background-color: #ececec; + min-height: 45px; + margin-top: 15px; + color: black; + margin-bottom: 15px; +} + +.project-list .project .title { + font-size: 20px; +} + +.project-list .project a { + color: #006dad; +} + +.project-list .project .desc { + font-weight: normal; + font-size: 16; + font-style: italic; +} + +footer { + padding: 30px; + text-align: center; + color: white; + margin: 0px; + background-color: #6b7078; + box-shadow: 0 50vh 0 50vh #6b7078; +} + +footer a { + color: #2495f2; +} + +.test-links-bottom { + background-color: #ececec; + padding: 10px 0px; + text-align: center; + border-bottom: 1px solid #d3d3d3; + border-top: 1px solid #d3d3d3; + margin-top: 30px; +} + + +.log-view { + margin-top: auto; + margin-left: auto; + margin-right: auto; + max-width: 2000px; + text-after-overflow: left; + text-align-all: left; + background-color: #c9c9c9; + border: 1px solid gray; + border-radius: 55px; + padding: 10px; +} + +.log-form { + margin-top: auto; + margin-left: auto; + margin-right: auto; + /*max-width: 1000px;*/ + background-color: #c9c9c9; + border: 1px solid gray; + border-radius: 5px; + padding: 10px; +} + +.log-background { + margin-top: auto; + margin-left: auto; + margin-right: auto; + /*max-width: 1000px;*/ + background-color: #c9c9c9; + border: 1px solid gray; + border-radius: 5px; + padding: 10px; +} +.download-button { + margin-top: 50px; + margin-left: auto; + margin-right: auto; + text-align: center; + /*max-width: 1000px;*/ + background-color: #c9c9c9; + /*border: 1px solid gray;*/ + /*border-radius: 5px;*/ + padding-top: 5px; + padding-bottom: 25px; +} +.sub_links { + padding-top: 90px; +} + +@media (max-width: 979px) { + body { + padding-top: 0px; + } +} + +#upload{ + display:none +} + +#upload_link{ + text-decoration:none; +} \ No newline at end of file diff --git a/templates/shared/_layout.html b/templates/shared/_layout.html new file mode 100644 index 0000000..33a2897 --- /dev/null +++ b/templates/shared/_layout.html @@ -0,0 +1,89 @@ + + + + + {% block title %}{% endblock %} + + + + + + + + + {% block additional_css %}{% endblock %} + + + + + + + + +
+ + + {% block content %} + {% endblock %} +
+
+
#} + + + + + + {% block additional_js %}{% endblock %} + + From 930689a3d7cc9b3f990ca4020a2087c34a415049 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 1 Jul 2021 01:29:22 -0500 Subject: [PATCH 035/168] added view_modifiers.py for smooth routing, moved ./webapp/ files to ./views/ --- bin/view_modifiers.py | 45 ++++++++++++++++++++++++ {webapp => views}/delete_cron_job.py | 0 {webapp => views}/delete_jp_webhook.py | 0 {webapp => views}/delete_okta_webhook.py | 0 {webapp => views}/edit_jp_webhook.py | 0 {webapp => views}/new_cron_job.py | 0 {webapp => views}/new_jp_webhook.py | 0 {webapp => views}/new_okta_webhook.py | 0 8 files changed, 45 insertions(+) create mode 100644 bin/view_modifiers.py rename {webapp => views}/delete_cron_job.py (100%) rename {webapp => views}/delete_jp_webhook.py (100%) rename {webapp => views}/delete_okta_webhook.py (100%) rename {webapp => views}/edit_jp_webhook.py (100%) rename {webapp => views}/new_cron_job.py (100%) rename {webapp => views}/new_jp_webhook.py (100%) rename {webapp => views}/new_okta_webhook.py (100%) diff --git a/bin/view_modifiers.py b/bin/view_modifiers.py new file mode 100644 index 0000000..2f63dc1 --- /dev/null +++ b/bin/view_modifiers.py @@ -0,0 +1,45 @@ +from functools import wraps + +import flask +import werkzeug +import werkzeug.wrappers + + +def response(*, mimetype: str = None, template_file: str = None): + def response_inner(f): + # print("Wrapping in response {}".format(f.__name__), flush=True) + + @wraps(f) + def view_method(*args, **kwargs): + response_val = f(*args, **kwargs) + + if isinstance(response_val, werkzeug.wrappers.Response): + return response_val + + if isinstance(response_val, flask.Response): + return response_val + + if isinstance(response_val, dict): + model = dict(response_val) + else: + model = dict() + + if template_file and not isinstance(response_val, dict): + raise Exception( + "Invalid return type {}, we expected a dict as the return value.".format(type(response_val))) + + if template_file: + response_val = flask.render_template(template_file, **response_val) + + resp = flask.make_response(response_val) + resp.model = model + if mimetype: + resp.mimetype = mimetype + + return resp + + return view_method + + return response_inner + + diff --git a/webapp/delete_cron_job.py b/views/delete_cron_job.py similarity index 100% rename from webapp/delete_cron_job.py rename to views/delete_cron_job.py diff --git a/webapp/delete_jp_webhook.py b/views/delete_jp_webhook.py similarity index 100% rename from webapp/delete_jp_webhook.py rename to views/delete_jp_webhook.py diff --git a/webapp/delete_okta_webhook.py b/views/delete_okta_webhook.py similarity index 100% rename from webapp/delete_okta_webhook.py rename to views/delete_okta_webhook.py diff --git a/webapp/edit_jp_webhook.py b/views/edit_jp_webhook.py similarity index 100% rename from webapp/edit_jp_webhook.py rename to views/edit_jp_webhook.py diff --git a/webapp/new_cron_job.py b/views/new_cron_job.py similarity index 100% rename from webapp/new_cron_job.py rename to views/new_cron_job.py diff --git a/webapp/new_jp_webhook.py b/views/new_jp_webhook.py similarity index 100% rename from webapp/new_jp_webhook.py rename to views/new_jp_webhook.py diff --git a/webapp/new_okta_webhook.py b/views/new_okta_webhook.py similarity index 100% rename from webapp/new_okta_webhook.py rename to views/new_okta_webhook.py From 6730e5eb80cc2068ba4bafd57c6d6615916606d2 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 1 Jul 2021 01:32:41 -0500 Subject: [PATCH 036/168] The layout abides this jawa --- webhook/jawa_receiver.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/webhook/jawa_receiver.py b/webhook/jawa_receiver.py index 6f1e21d..2be5887 100644 --- a/webhook/jawa_receiver.py +++ b/webhook/jawa_receiver.py @@ -1,9 +1,11 @@ + from bin import okta_verification import flask -from flask import session, request, redirect, url_for, render_template +from flask import session, request, redirect, url_for, render_template, current_app # from glob import escape import json -import logging + +from main import jawa_logger import os # import requests import subprocess @@ -12,8 +14,9 @@ jp_webhooks_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'webhooks.json')) scripts_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts')) + blueprint = flask.Blueprint('jawa_receiver', __name__, template_folder='templates') -logger = logging.getLogger('waitress') + def validate_webhook(webhook_data, webhook_name, webhook_user, webhook_pass): @@ -36,15 +39,17 @@ def run_script(webhook_data, webhook_name): webhooks_json = json.load(fin) for each_webhook in webhooks_json: if each_webhook['name'] == webhook_name: - subprocess.Popen([each_webhook['script'], f"{webhook_data}"]) + proc = subprocess.Popen([each_webhook['script'], f"{webhook_data}"], stdout=subprocess.PIPE) + output = proc.stdout.read() + jawa_logger().info(output) @blueprint.route('/hooks/', methods=['POST', 'GET']) def webhook_handler(webhook_name): - print(webhook_name) + jawa_logger().info(webhook_name) webhook_data = request.get_json() if request.headers.get('x-okta-verification-challenge'): - print("This is an Okta verification challenge...") + jawa_logger().info("This is an Okta verification challenge...") return okta_verification.verify_new_webhook(request.headers.get('x-okta-verification-challenge')) auth = request.authorization @@ -61,9 +66,9 @@ def webhook_handler(webhook_name): # f"Webhook data: {webhook_data}") if validate_webhook(webhook_data, webhook_name, webhook_user, webhook_pass): - print(f"{webhook_name} validated!") + jawa_logger().info(f"{webhook_name} validated!") run_script(webhook_data, webhook_name) else: - print(f"{webhook_name} not validated!") + jawa_logger().info(f"{webhook_name} not validated!") return webhook_data From 059b94a7d38d12ac86d68cb873a7db50a8f2a89d Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 1 Jul 2021 01:33:39 -0500 Subject: [PATCH 037/168] 3PO abides --- static/img/jawa_icon.png | Bin 0 -> 15144 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/img/jawa_icon.png diff --git a/static/img/jawa_icon.png b/static/img/jawa_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0d23280b0a4b10b94e401924dff17b3ecf7ad02b GIT binary patch literal 15144 zcmXwgbwJb4`}k{Pz=%;(K|tveq`O9kq%g->WI#r+uCG3N`m6{7GZ*-G}WFh4~wu_x`kfq4Lg*(nYNI+dK(v z9XbpOIigONdD1p-I%j11f~H2}PtBCI+*|40-4n6r{wrdn9-aft9RLr(Ed|GI%??z%Du&v`7{=kOBgE%k{qEf4j$Be7fHUZ)ycfY^voqLe``1%X zF61$kO}uw{n*HS8{_Kli8(zBy+4J@$hGZ)VXvt$;G<^(>O1k3e`iu_ed@$DJC0l!` zJgvM{_xtj5c3PFvc%$7a;(6=Ybl*I52&)>Vyvtx{nL-^}Kvx!ee{J?aVLtT(9^eUu z3Jvxt}etM;Fd!&l|-KN!Fk7PP`SP!vs#{)?|;ilVi6?%s3s zGz|6kfi|ZS)RM#W7f;p2&|9y3^77AsPtGrx|8511Bg4$V>VB}9ddG4@A z#hV%EJpI)4@2$skTaG;&CS_xaIjE{&|D~I?(`F3TB6>bW8g?iCt_{0Pxra3OiP(z2Outp1D^JOUZUL%~!JP7iq$*lg{y|G>Q~fJl5I+>#82L8=)yFdd>s7WfxO|Z+2z7YE8ZOaO-4dh_)N_avZx`t4163I;;ej z9n-z?-j;^Yn0|IjF@8N&D}v@%A13s3Ha`;eC=Zjj-7nWgqfMhSt(9vr#{tKBa>Ni( zo~{Vxnv>P4!J}SF-dQnb!Bcop5HOB?rFOQ&CX0|(=G|c-r*AT5X;c_9+Tc#_SpEao z5}g`K$9CgY_DC4ma?cb9D1WJxeW`NSxnTc4o(lgpm!?nQkQ{;kP1ULrdpIZ_bc_%9 zKZ>g>yCvJJU+@UBD;jte8(r2i)~N4ZxLhlqZ46$SfO%8Pc38iM#sUdL z<9Wd4$9=WyQHC4ixjlc)x??Y)Gylhx{OJww|75FG^J88XETzP!CB$9G8g(R$&$iGQ zCgHu`(&qlo)K%w}ful0?#pU#TfJ>hSqKc*gbWdh!0_z{}Y)EuxtK8i-Ytq5pH4?Eu!NF~6e#KAuLtZ@D!kXG-z6cHaxq9P6iF%2+E8+( zhSY9H4VT^>A!{i5?$=pR=;*maU`n4X2}6shFy$7-Tc!>^0!VmFRR^PeMu^}H;3o_lJlP}sezBj7G@AOO0Cjwwc$ND0L3 zXgjJxkIOx#{#Mq^;c3A_b07%Qjkt^DWG=BL%$rNcU#W)wH50O_TBx`$qA7 z5IJD7XAHew9~c`Nn+DB%cHAa#U}Jf3Y7D*w9JP>nBQ)#Ox`lh5=26ow0CudwApvZ( za5jfZocc>XBp}@IF?^81O`0JY&Nkq$CwZty0Hpvk0h9$g3oU8}QAceTZEsM{q&clj zb@dJHFZ4bx^k??i`Gm4}>&KENWLgMw+@koLPw{CRJzbONvUYl(V#yzOS4LC;f6pzTYe}u)_ z+33kYm|zM|h^UVq4ucItSu#G@r{pP86>Tc$v>{?5>9ircSIh zevC!OYQ~FW6}?DS5LH5{Wa9|LII-+835}4)!OU_@HS~qa7Lh5-9T0zZsaoTx(~Xup zowYqjmnx=$+1%v}l65IcrJ91MK+e*#=@(jYhTX-Ax|zp#hVVNfrPCq)TY73D*c{qa zfaLRW`nB*Frp*X@Q7*7IJ{S^AmrP%9UkWLbo-f{nJssDU$ceF=bW%H3 zH>`zeF;Mmyqb^~ELMlb2Cx4@6S3NO2km8ZXEpwi*__ps%peZ}o*coW)2klQkM&5V9 zJ=7jWX=v9h4sk&WdzsqYW{Omw+l=Os3}z@NqInPF8Kv|&HA=s4Su7-``Zppupr(oA z10VC}Rud4qr6WK2S2paOsfXEU*q(4!;dZ&tOkRGmte-5S{{a(nqe*5|=?Jcn#!sig zqv6T4Wf|w+{2{r7eI}IqM)<2{TWl#vZF@bH*J9D-vxk~f)Sdhjf_?cCn}*LpzHc_G zd`zNYDzQv3|(=DF%@5a`w?(#$489m#FEB_gj{t+xt$j6ZiX@v2vcP|{q z{)VPs9PP?b)7stZ>KxGj{F2$Yt~M)wYpJ`V{XmyuhH!VGViF8v$Cl;9pn~uDcdQKG zLfM;-t%kC71sdM{L5VKdsE=usLZ;sg{JUd-N3fU1XY)J0#GA}rJc^z{QLMdYGF-Ak z(bs>CX^EYKCK$fR_9anvTDgfU>1*8=KMHT%djjvwrfGd^VX~A?@yR-frW(vt*PG}qELw+LpH6fP5}QW0HPhS@jc!(HAv$UucDPEnV-~zJ#R10pV6hiFJltppzgG(1R$f4b=w z(Shy>)7eU8pox0_eGsgCUU0uAa z##sdSb@0wzcQA>v+4raEPBSMzB?IBAXqI6E!p?y);spIi zOs{m(iXA=P^>{e80*U2aqjo+bw-Dj)37B2yW>p}iS9)fHeZHg?fj-F=s?sZ>jVGd< zhIAi?+{i+;-UG9=lf8cLg&*93gx6chFddTmdV${zIl`+)AEH9;ykuf6FAlb+=W(P3 zUS%u9-EXy1baRxLzf5chSb%m{!juME-caK!T5+`cI6JaFmuj*}L25KyXyi77Wzk(5 zds~^Kb(Rp!)^uJfV)B$(Ngqu$1hhWq8brCvV5`=P%*l{cUN}1sd)9GKi%(du8`)NccHc0I|tmsbF2}@tYQZEppucbq{~+~ zN~C#T*Sd#S5bKYu5P$s-$4w9#7vLkAd(t;yzv~|;-kbEr zO*eJ~Aw0w5jSRA$r&O>UYYf5MZuPU+v5p1&S`^`;=~B|rc5aw+T<)6L{MPPA%h5nY znUc9_F}!MJ4cmhxhWE>0>p8ogt#-`sJo(2K@qu!UTG(W5ykkXsa!wD;Jrujd+#O$! zM1&S3u=rS6N7zvb>)x2Kn&IsZ@am9xed6X-6&(E+bDHMCvS%q~4DmtH7IHgIpHnV0 zTey~AN-;wohg}4?{5jdL^mP6tq{L^aASRgS;u2(+v@6)5{CbWFmEAD(A?c*-6O;7V z1yNe1yy2T68;?gpcI|N6PUY7>*Yaf5B)S=ArOY3yd zQ>Eb{A9(TdCbIG_%OH-U+oK`Qt^S*<==kfX$qiRV}mkjieD;EyMi-e zEFTc*#R$~M($VJ03Q;%D_@*=kc}qlDne|FU=&vK{r=6cTl^N`=5p-y~)7KTEvq2tj z`fI_*XY39aU6XT5y;M#F|6;_8?%HP#7Bn2sA6?c$8m;`O4To=-GWTIG^$k2Fg;Gp0 z%3Tm0t*G6IR$d&yy#2x7q_AGajf{%h{z&L%T9^3X-ay>I^j3MX1$U~J)x)yD<$G%j z3sQ;U(D9jIQWR49N`jMvgYUbJr~9%9Hx41WdtS4kszrI=Vy)uUuj%hf!^m^K60BaQ zmM#7dVS!V8X&6vcPObAh8^(>f@IU!`Ik>}r_lyxu>9xiXa{#8*y~RA9GrHo2s(`_E zzxx^2`?N zTPOi6-4eF*hqVN|b@VX9W&?`^puRF7=t+J1a2a3;+v~b=Nu;UCyI?4xR+nkb-+ZLl zQb7lZF`&l;(Tio8c`aa_aF{=Wz}CMi(!-#qw_MMN^5x32baSpRJX=)cg-qc8F^(!$ z_7U3cAQC;L%>%x$W{@{B{xW~04R<=jlJR>Rd8Y*jihtzOl1UbR6%SyBCcL|f%)L9g z|MBKBL=W(d(Qx!aeF`TFZ+$K(7^R^{OgJs~CGet8q;GQAfipA?S2Du&!n32j*~Eh< z@G_@N+@XeHcO~$^ho?TZK4jr|H`^OQLi$m61jjFJzrGI{PfL2NM-htzxWxt%7$?}O zy8@gSG)fPq2xe=4aR*{tF^!eXPSSMu>{ zyO5i8pfj%-sHw@;tTuN(=0^;*7D=RTN(};d(hDx(>hC3X)nke0FSuueA1eGy4ft^i zYLb-m<&j7b%U(BEN&f?!Ey)^RV86v*5LYJ>`)kaCXI>D7djesPzeoE%Aj0B6f18P8 zAm&-3cZcC^UFn8SDfnrluB^Qg_oEqftj`5$b-HAvCx|>q1Qei>9*A%2j-qzfR`9|D zlJL|W}6mNPqvDr*mgaq0II0T zA4tth6tJLcrLWyN+?=l%#qZb>E}3v@pudTDa^^v(fZ&VXx4_1?oF*h^iR9i1HQ<%} z`a0+q$r16!{_Lm^pNVA8CemPI?%04c@RAg@V=1ffSV@)Dho1mg2&1rn-W%{?!Vjl3 z_)dEW2?WZGlVN#?p9L+gOJBb;blE2K&M)uGJay@a8jy48{F`=C4_H}s%r#uU(?w!e z`x}mDl)(}y&FlIWUGNaC1@8jBtMv38gARlQa^#n;V9Bx&R;&xcr&oJe6VQP(d z)wd=KkpdWAf6&!EYy|#I_z0LxZzBh~@LzxbIwJ*0CKy~>22~?M_Uo^9d&7-8t0eiV z>jelOMm&IKmHDE)`%}ryj;Zp+y?W5=iF-|9lzlG4SAYh3M!P9WQ#7t@gucQn>)5}d zc)QDR{WXRZP+eYf)?9W00{90BomZYg4-&A0Px1LAz(F}(-hw8Dr^r(QYXl@|1pzRS zh-6CDjf_4c+M75zDGOMzaqYOXGV{j|1C@<4#%~Z1{!m?|ew+o(poe=4nM5!F>n^fk zKv@NkG9~b`&#%DC0GMjH8~wb1_VE)%9LSm!7)XXGKQ`>+eMH$HxsY$eeyz{=d z4{J#QdB1%2rJiD`f%kzVh85xK-x$|`ml(w;N5 zT#(;iXhuM40|Vom|KhmI5VV-f^!SC;O@xfaR+13OKd%t``2}iQCOBsGGc{Fg$u0}i z3=)g$Ob91Gqdnk>FDy9eSa?;NMEP$j*l;iP`6=_kMd0FyNITN=kKCE#dV712P9jy= zY-+{D<$zX~cm;$;$46AWAA&Z~?NUd4o5pP8OWloX#t@PX&-;|yYi*|-xgA09pT-k7w@qz^H8&>{@#O~f5Hxs+I)h(X_5!{^o;qdKF%BPfpemxQ_^q2{S z=P#Zn$oq}rlxH1lC107>ZM-EN-ZW={p&#quia_pQQE8>NSd^ycXr@4i5A zGz7X9AlBb*U;AK2pJRTvul`!qOn5*Nz-M@!+?O%2tF+G*H{a}G>oF0%^fl3E+6!zZ zCDW9yF&|e{P~ZWC_>}0!HMew*$d+a!w?-Zic$Po!{M>%@QJyr7gY76eX}27EVOaX+ zhb;a^iR8u`(uEKfO;uF^kYC>Le`qMeZ*C_zTbV?GJk7|6Y-LO6R*iWH{@d5{Q<` z{HkbTzmQ_4P)?-AUTDppU$*sqd^o=@=)QBjCq+nEku4S}Ap3dJdZ=yz$NeIXstc_d zBdQJ0eR;vC^q%1H9KRbNehmxP@0c21xC2bjq}I9lEyl7IKBAd&T_>{BlC@>26%t z?o3JGXm|m2j)DIzcML%&zszUDvWWf3DkK4g*5|T=FG;Mx4Jsv_6*W)&x0g=Flm5A= zdz+WCX)s(2?`0*{I)`GGbe{LpX|_nliitmga(V%k1U^}%fM7k4dv8Y_`R%?xiJ!R} z_`~l0X(x{^LlwBw=<%I139z(cgjWx(o}2EBIc z&*v$sBByM15nnw*Dc#dRRXH)%u-pHl7>$@&BF>gdO%NC`MUtT(okg!d~+ zpo9cX>TaG@rDDhQae?${nnT@r#ewmcGRPS{L24#ffs9~NfU}N_!J7WIr@yzkxTr|= z{7=I&nZ$wZ7Dyps5J8yl4!x4<^5>kp#De+@m{^+m#SkX)W7lQM<=1) zy%>iITb3b(X3YiH6#f+Lt#P^rhj2NnXjLLEph@=bR&aqPF*RX|D9!$w#@MHU;VcVURS#`$ouWoQ&!nKsRfFsdaGeGYJD0#fH3G}1f zRz29w8(V*~@ts?<=oxx5=)bwVhOckdIZsej0E`|$$RswR@^LrHMsIy+Q<(T9llEnVVw}n{dcLAU zzXS#lg%6_)fPWrM0voGh#~EPhl0`^Fuuxdw>0AbmYGx{w=1CF#+w6j_n1vt-kpxMg z5cQ)VJQw&k;-oB;`#}?)|FyAs_hjuYb$sVhz(VVjQx}h;LWlI*LF{4@ZX}%40TWB! zuK6%@WRrkvm01$EQc19Q;FL%Y#prx)RahDfl{m`rB&Sqy02Q+D!%TI|pTiB3bUK;@ zvU6Ds40k(O+6GDNG;g0vo3ghwIw#z|#gUVtF$;vAj!H8#+7T@%7W74!O?W+UB=;pk z!_X)xz#Z^w25G7JPXjSRi{7p%80yg2`o@u80m1xhmwyTbAC{B$fF_fLth?aoXEFdi zbET%7U96-G!TPvE6@4)T+Upo4$U~N=O#3=|A98F%xJOJtf4i*-e~;D(`u6eRpX*1B zpg>5pDt4Z5$4WK118x#iK*PsWvaJ?FYg!TPPU8vr1#skS>DesVW*uEJAdm985(FpO z#eWJY^~r96#M&aXc+gUfs=u2ZF8Gipc)a zNgSAE-ui%lcDv2j>vznGgL%^ER=~HATyBnVg>_AW%s0pu)U73f{Zd6`d$%>Uh;Pod zbNmAX+dn^RaPr?sav?&K013Zf=tBbHl>04UOu+6;*Tq8X>MvE1OGQKYo;%^ZBIG|< zNED6k{+=uBfunZi6Mqzhagl1szc-*#QY%*)@!fy)oAj(vEV1kFCF|`h*U&HB*oob$ z+1+POae`7;7Pz!H8jez|K&h)#!O;D(-||3XOi~*2hjlZh^`R(VOSpT{~h*7I9#zsM(BhzZJL z6_}~$BgyB!IWB0C{bhlHKl0LxWm@%H{p`rnXM@Qpu_cZtNBz`O!(}=wz~}#i(BNM< zpiPgBo4-7og9L1(irt;zEF(+)Qe&jiHD5joZxRPLF%pok05Jxll%>tt-_97e_3<~` zH<82eHTKj9{8^fHJuqP#6!W$1t>6#T3VtYoAo2wgCHGM$BEwycFR;C&IX44T$?@*6 zrSwp4iju_brs;9tNQRLojCN8~(Ik_EkjQooD8u^Js3h9Og~j&oE0_)mQiHV^ZL712ZQo~i7` z>!yg^Z9oVZJd;iEgNfZy9;`dwU)Mxf%*?pru^${R+<}mNRf3NA_@pNDbh9?=di$?U zG7d1NW+t%c0ED~reNJp#X7IgP$5fUvlOg2%CsF(~;8!KN{LJ;=N`t->fc&{?IA4N9 zNZ6FX(3Z6+KOsTsP$v~T{3?+{qzsJ5ZiY)E!4P+{<19}fT61VuF^_#dDyv=sLqY%Z zQ8o=(t^@!4a&8=+`!W|rU@i`+CUANIVH`igdNZ$`<^4JGWcNRP6ZuMt@n_l-eI3^? zpbbK0(f7^aK`%hwCs9A{e95`1ffc#>iX(2fsEPQb#>*<%+)PyVEvXFDv(coP=QBl>5kz5kfDAug&5t?d=WkmyRAKA-Ky}!M( zP?;O4a&rq3c@=5jtQljQTkYuUSbmU&1Ch9JN5bds4Ja;dTq*fy-8UagXrMvY&YFHKf#{+Fc={b`3bPo-~9<4Baaqc<8d z-s%}VxVf_YIab}cb$1#Yxy~XO+KC>m`D9?tKjbA(WjvntavB~N8-ZwkzFX6>U!Uje z$eNv6fw|loC$0`)&;Jv-T(IK>JY8z@f7s8@Wc-^n_&RDxVKrmnYhq*^SJ00j3bT?1 zO~qDQA5_vib*~34Uk6In1_&CFT2@e6z5#!MjkHBbH6ijcW8~vV0$U<34JV=9E(jQKjC` zij@0CP3|eg%skWN^KfKiqC%cs+P7Uq*#S$&(CWk#alyw!tb=ukOK0ACqh!5UuA$(T zP-@m9RdI{{=wXP3_x`3Paohv$ZW1+~!}>oWG65P!qddU==~YA{o4UmY_m=+4gMW_W z_9yBhOm9&TAl=d5_s+0{=RJ05mS8M)KA>tWkc~7o^*)#mFiC?|RJJk!^9uCqs#WuV z>u->^CZm|oRafOLU-y*3666=qnfPFI*_f4~`C!of_0Ac|bQl1bRDV}dJDvkpvga4W zO3M~vqvj|qAufZubZJ1oeH|e`-_$kWiIBKWT8F)u4-ont%4YjZmx};MHI7s}ZLr+y z%#_YwO|dSxJsipw5EzjU0j@ikraGWuKoDT|R0EQ#v0M_BdU(&s< z{{k*izgzJoB^h?OSBFqi@17iLucY-XKywkE4f0OCOkt+W^hFN}TPC!!!x2 z+>1mMV5sP3l#H%x;A^DNtowv9I6Y~TQ%zXs)B-6rcE1}2d3U)G+HG# zCPPCUtkRW@mImnUvJ^|@o&&nwP<{%7t_CM~J#In!^EcNU_MzeAK=1eIPjCiCxvH+4 zcVBN|#&%-onHuBXv(W%)vAUvk8H`fneo9T?2g#Uqa$kCpAOJvL;)ShYdDfPw5Yjdyu(SE?rb7{)`Y`5MZ~$S(pmW zlylPiYm%_!t^31dZ^p`f8X&9@Yaj?6^SHMIoMm6)4oAu*K7M^wzE*Le@TV@Os=o;$ z1n?@K+xC{hP|Vj(>#y#t#q70g+`82l)O%;rwR5lR(%q;kyNA9j>y@sUDMi@pz3fAzvp0ymKr z!*3KRNyl+VyEb;KRA$x=J^j}0eo6qtzazxqH~ewr^XpV)moU^i4n0TQe?>Wo#HtEE z-vKu$KiO|6{jjw^>Qhc%y>bvlNeV_4`2LJ{w0n**!8&*wSsM_-;oRHw)nz1F`;-6> z$;0?r>=M6|1L~B_Hc|a3tDlKEWoH6t1wIpU{_ak|`dFf(wZUI91F-0kSAM-Vmtsud1mrDD3MN1i^sA)r7&z9CR@ z($&xAP z0;=H;v~f%{n<5_49)UXe)(-ZaJbf8f-kU{Ytblt`FgvFP)fwg9t!&BowfQ@VW*p3x z#YCxj_e5ENX7!dBIwn$`C)4ddKg=)jk8Z3lSn$gcX+VR4J(6{aU*}%XnHS41wQfy) zWJ&8m;`_cc%iool0R!mbd~QA+e2D!LO$x~NO+7%m+*}}o{Z?*~mIfj4d_T6mqoZIA z+@K)s1sx+)`*CN9V_vDt_&LAlBZE}mloGteU8dn+H-nZxdFJ{o+&42H9Qe!{-KDNa zz&_cZasm15-B1sNU8pbzj_KgDyDZ~>2tFp;a;1dWc`WC_Dzb6WNQwpA#ebUOdr`5< zTu#3V^G$xI6QOn*wBq!ZH1_QR%@=^@cFQ)N)FO(bjo6S#2^s^O8?#9VkhP?kqTpHj2(Eun6E6_JD zjE!1jIm{2PQc(qKVzqnsx?Y$-fXU?Zd8JuoPc-D5JyOivjc7qes(RzzkYD><0M#R1 z^RF#(6nWG0e$J)O8hPocFV*W3JhFx`V9WU_2+(!rE5SddYcJ@_#qfNs9`4n2kf;X$ zZlk-;aRDQTZP}q9#c0*HV*6Ehza-57n4C^C#mfFBWpR~ih;V|h3!x*s)aS@LSDLm& z&}yyiPmc;eqD&bIf}@!k)#@yER73a!YypDH>VLEDk+QqN`3asq?i0qZn}kh2KA+4E zOdC`bF!eBc(yYJ=rI-7hInmm_CsAkH!Y1jt15YV&S8AUOgvGv{pH;m~4E#Hmw&4h-^RN2GVO2@hu)7<})E&O2|*RKJ$^I6UE|ECjsq zYrkrg!}A5_v$^P0>Mo4cDCy`SLjKc$tg`$)Z(^ui`MZV-YD-_%@FHq|qJ`KjOZi3n zDZgAW@V&p8u@d*g{Kkg-%OdK{Bca4MC3tfS`NeIRFvox~pu|8Mt5qFdj`#klFQfo> zVfjnK%Ja{DB# zEE!9|w4j^$5wkdx9$Wrh+&e1ri|3k(zBGe)v%dL5ONL--N~0Z3vVLHd=T6?Npu=#| zS}177$~)|1g3RaW8Cq*xcJ-M}lK`L9Tn1Mo-RB zX=#%g2+zj5d^r!e7}xBv9@Y^c@ihd!Qi2%p z;zVUl8zCiCx(LObtvywzP>lpM?y1#0mDEPQOrpRUUsdd@V{x;_=ZgcHdOSsA2% zg{BjLHq6LOOx`!J%zrF_|G1xqwUhzz@3~Du0Lqt-J;gWnTo0dcD1-&)_7EX&FKB)Z za7H&XN`e6F{j68L_y1(Hbv6P9Uxa#_|1^&x=N_++u5srlw<`m3PBj8K)zqGY)Em2A61>j2N2E*6 zJS9}r@*)-CetK>1rfLn&IJz5#ogc_6-x>+3 za{_1XEP~uv=0n2~=0ocjCzq!=H!-h=k@_g!-|`MnJGh(K&j!M%(KUFeSYKNLYSJXEnOxEq=gW{#H`8Kj0r?nZD$eCRnM18Otbw=okAIN`J? zrJ?f(v$Zxp9;&zSL|l6ksp<|q|02)fnjQP7x^80cPs}5RZbex}C!E~rVNtuc6i8PVu~HD9I3?J+^bM2Q z7oXKXFKQNIeZQL?=3SMTG$Izd!k==Ju6;0K0~VdlBruoy*DEH|R^w8%FHX9~Y<}`dyKwKA?8pN7hjYS{9%AXXS;D&)=WDgN(UN^t zwCfsiwn!uoh~d_OF>A; zaU|gFrdl}p{rX`oQ#x-Mokrt7(jR5u$~&>=UISf-;hn~|HEQw?pIc2KQ+y^Alf9JZ zoE?)k!nJzUfgei-6h2;qAw91~tdoj6W$@ck9DC|MJJ3mJN7(pk#jTYD69~$?QkIhegOIGhA%W z(+3=oLjFjB4;>Yx6(HiF%LZM-VFb6zQL7eAo-3D!U`B6$%GxENa1twUq%?-feby?@ zm~3>uZHWcEW+5?mBSrC*?#_rthrnC}BRX+4lHEd(^!r$`EAO?Rv@LO~+zHyDfZQz5 z{5#dxjl}07pcTzRnU8;|LND2Zlc!k@vO97+<8U)H5U^m&lKVJE^+^vEmIux?bHZZ% zE_H;~sELIGC3|}!`&_sq9}Cul)7>Y&6|?n3DWcfsAq`SH&5H(clUB~yHZ%*FUEsr! zC5VUre*QbMC@0r(xsPXGkv?KhAppm>c3tz`l_Md=eiqVvaRW$Mgxw=+_6(I)bT#e1 z*1`n$tD(CYib_3a|DLV2b?W@(-zLWQu#n^*)zz0y5%t)UHbG^GutpqS1~@@C0;_ z{DV(#6-Z2OTw?gDyr8-)y2TnILpjVs-1;_rusmZ$G%qJn?<3_bdZ(Wz9=#=}>(pSH zTpOaTi%Y*LFD0&)aoGz`a?u_wLRr2Ub4$o0OS5rN9+M$IFA{m1Ao!f`Pj_c)g-9ZN?ZCa8`GA?Fi4dGK8k4}KapF3qK-%vVd zs=$VJwD`2d(^mzukIGA;oJLdCIXu&|AQDs6%AVK8t~I|iQOoTv>eP$msNIH-X!-mM zXTKfVlA5cf7FY7@_*#MI?X=HQz4*jWAw}k~K{~6*deK0Md*&Wm(j!E!))jtv0(f7= z?<_knETYyig=K#N6x;vn`fv|eM{WR60I*|fJ#3f(8p_Wcu7>#$ zz4leKdj9}?&_(#HE+e<|Fx7mvMY^iLa2>}=X!^fdd%@l<5!V4v*S1JP`^7!W&*ZUAB5SL zqARP6TxR_dPMXlf{RS87a`k@2@{8E9fbMPHOCo$C;u-X@k;i)G90H4*x-#q z1ipiXfEgVhfSfuPvXx-CD*SiUM;l`n33&WkPXcWIo~Rp-8`cr@+v}VN&sA!|g!7ks z=7T~K0P3-{N8&{MD5#P)eW;zB8Bw=!h6E- znQItb+$B%DUC8Igt7n!#K0UE*whE;fQ|9EU?`9Vw#Ur$>viH)l*`}3nVOFU?*`jE+ bbnq+;M;79|*$>$XECJf;`f8Obb`k#%ii#CC literal 0 HcmV?d00001 From 6edcc275762b03c8a334555ad5392bb97454ff91 Mon Sep 17 00:00:00 2001 From: ball42 Date: Thu, 1 Jul 2021 02:14:07 -0500 Subject: [PATCH 038/168] fixed logout --- templates/shared/_layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/shared/_layout.html b/templates/shared/_layout.html index 33a2897..2bca25a 100644 --- a/templates/shared/_layout.html +++ b/templates/shared/_layout.html @@ -46,7 +46,7 @@ {# #} - + {% else %}