From f4f18d41eae54a60b73b3e995ebc57f0f1356152 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sun, 14 Jun 2020 17:58:18 +0100 Subject: [PATCH 1/2] Modularized JSON storage user_template and other files are now accessed via JSONStorage, which has dictionary like attributes for each file which can be used like a dictionary, without the need to manually read and write the file. This was done so that other storage types (e.g a database) can be added in future. --- jellyfin_accounts/data_store.py | 66 +++++++++ jellyfin_accounts/pw_reset.py | 25 ++-- jellyfin_accounts/web.py | 15 +- jellyfin_accounts/web_api.py | 237 ++++++++++++-------------------- jf-accounts | 38 +++-- 5 files changed, 197 insertions(+), 184 deletions(-) create mode 100644 jellyfin_accounts/data_store.py diff --git a/jellyfin_accounts/data_store.py b/jellyfin_accounts/data_store.py new file mode 100644 index 0000000..54d5517 --- /dev/null +++ b/jellyfin_accounts/data_store.py @@ -0,0 +1,66 @@ +import json +import datetime + +class JSONFile(dict): + @staticmethod + def readJSON(path): + try: + with open(path, 'r') as f: + return json.load(f) + except FileNotFoundError: + return {} + + @staticmethod + def writeJSON(path, data): + with open(path, 'w') as f: + return f.write(json.dumps(data, indent=4, default=str)) + + def __init__(self, path, data=None): + self.path = path + if data is None: + super(JSONFile, self).__init__(self.readJSON(self.path)) + else: + super(JSONFile, self).__init__(data) + self.writeJSON(self.path, data) + + def __getitem__(self, key): + super(JSONFile, self).__init__(self.readJSON(self.path)) + return super(JSONFile, self).__getitem__(key) + + def __setitem__(self, key, value): + data = self.readJSON(self.path) + data[key] = value + self.writeJSON(self.path, data) + super(JSONFile, self).__init__(data) + + def __delitem__(self, key): + data = self.readJSON(self.path) + super(JSONFile, self).__init__(data) + del data[key] + self.writeJSON(self.path, data) + super(JSONFile, self).__delitem__(key) + + def __str__(self): + super(JSONFile, self).__init__(self.readJSON(self.path)) + return json.dumps(super(JSONFile, self)) + + +class JSONStorage: + def __init__(self, + emails, + invites, + user_template, + user_displayprefs, + user_configuration): + self.emails = JSONFile(path=emails) + self.invites = JSONFile(path=invites) + self.user_template = JSONFile(path=user_template) + self.user_displayprefs = JSONFile(path=user_displayprefs) + self.user_configuration = JSONFile(path=user_configuration) + + def __setattr__(self, name, value): + if hasattr(self, name): + path = self.__dict__[name].path + self.__dict__[name] = JSONFile(path=path, data=value) + else: + self.__dict__[name] = value diff --git a/jellyfin_accounts/pw_reset.py b/jellyfin_accounts/pw_reset.py index c46cd37..4b6c664 100755 --- a/jellyfin_accounts/pw_reset.py +++ b/jellyfin_accounts/pw_reset.py @@ -4,7 +4,7 @@ from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from jellyfin_accounts.email import Mailgun, Smtp from jellyfin_accounts.web_api import jf -from __main__ import config +from __main__ import config, data_store from __main__ import email_log as log @@ -42,17 +42,18 @@ class Handler(FileSystemEventHandler): reset = json.load(f) log.info(f'New password reset for {reset["UserName"]}') try: - with open(config['files']['emails'], 'r') as f: - emails = json.load(f) - id = jf.getUsers(reset['UserName'], public=False)['Id'] - address = emails[id] - method = config['email']['method'] - if method == 'mailgun': - email = Mailgun(address) - elif method == 'smtp': - email = Smtp(address) - if email.construct_reset(reset): - email.send() + id = jf.getUsers(reset['UserName'], public=False)['Id'] + address = data_store.emails[id] + if address != '': + method = config['email']['method'] + if method == 'mailgun': + email = Mailgun(address) + elif method == 'smtp': + email = Smtp(address) + if email.construct_reset(reset): + email.send() + else: + raise IndexError except (FileNotFoundError, json.decoder.JSONDecodeError, IndexError) as e: diff --git a/jellyfin_accounts/web.py b/jellyfin_accounts/web.py index 2ab8188..8907a6b 100644 --- a/jellyfin_accounts/web.py +++ b/jellyfin_accounts/web.py @@ -1,7 +1,7 @@ import json from pathlib import Path from flask import Flask, send_from_directory, render_template -from __main__ import config, app, g, css +from __main__ import config, app, g, css, data_store from __main__ import web_log as log from jellyfin_accounts.web_api import checkInvite, validator @@ -43,16 +43,9 @@ def inviteProxy(path): if checkInvite(path): log.info(f'Invite {path} used to request form') try: - with open(config['files']['invites'], 'r') as f: - invites = json.load(f) - except (FileNotFoundError, json.decoder.JSONDecodeError): - invites = {'invites': []} - for invite in invites['invites']: - if invite['code'] == path: - try: - email = invite['email'] - except KeyError: - email = "" + email = data_store.invites[path]['email'] + except KeyError: + email = '' return render_template('form.html', css_href=css['href'], css_integrity=css['integrity'], diff --git a/jellyfin_accounts/web_api.py b/jellyfin_accounts/web_api.py index d5b2551..eb34265 100644 --- a/jellyfin_accounts/web_api.py +++ b/jellyfin_accounts/web_api.py @@ -1,48 +1,40 @@ from flask import request, jsonify -from configparser import RawConfigParser from jellyfin_accounts.jf_api import Jellyfin import json import datetime import secrets import time -from __main__ import config, config_path, app, g +from __main__ import config, config_path, app, g, data_store from __main__ import web_log as log from jellyfin_accounts.validate_password import PasswordValidator def resp(success=True, code=500): if success: r = jsonify({'success': True}) - r.status_code = 200 + if code == 500: + r.status_code = 200 + else: + r.status_code = code else: r = jsonify({'success': False}) r.status_code = code return r - def checkInvite(code, delete=False): current_time = datetime.datetime.now() - try: - with open(config['files']['invites'], 'r') as f: - invites = json.load(f) - except (FileNotFoundError, json.decoder.JSONDecodeError): - invites = {'invites': []} - valid = False - for index, i in enumerate(invites['invites']): - expiry = datetime.datetime.strptime(i['valid_till'], + invites = dict(data_store.invites) + match = False + for invite in invites: + expiry = datetime.datetime.strptime(invites[invite]['valid_till'], '%Y-%m-%dT%H:%M:%S.%f') if current_time >= expiry: - log.debug(('Housekeeping: Deleting old invite ' + - invites['invites'][index]['code'])) - del invites['invites'][index] - else: - if i['code'] == code: - valid = True - if delete: - del invites['invites'][index] - with open(config['files']['invites'], 'w') as f: - f.write(json.dumps(invites, indent=4, default=str)) - return valid - + log.debug(f'Housekeeping: Deleting old invite {invite}') + del data_store.invites[invite] + elif invite == code: + match = True + if delete: + del data_store.invites[code] + return match jf = Jellyfin(config['jellyfin']['server'], config['jellyfin']['client'], @@ -52,26 +44,22 @@ jf = Jellyfin(config['jellyfin']['server'], from jellyfin_accounts.login import auth -attempts = 0 +jf_address = config['jellyfin']['server'] success = False -while attempts != 3: +for i in range(3): try: jf.authenticate(config['jellyfin']['username'], config['jellyfin']['password']) success = True - log.info(('Successfully authenticated with ' + - config['jellyfin']['server'])) + log.info(f'Successfully authenticated with {jf_address}') break except Jellyfin.AuthenticationError: - attempts += 1 - log.error(('Failed to authenticate with ' + - config['jellyfin']['server'] + - '. Retrying...')) + log.error(f'Failed to authenticate with {jf_address}, Retrying...') time.sleep(5) if not success: log.error('Could not authenticate after 3 tries.') - + exit() def switchToIds(): try: @@ -112,7 +100,7 @@ else: validator = PasswordValidator(0, 0, 0, 0, 0) -@app.route('/newUser', methods=['GET', 'POST']) +@app.route('/newUser', methods=['POST']) def newUser(): data = request.get_json() log.debug('Attempted newUser') @@ -125,12 +113,10 @@ def newUser(): if valid: log.debug('User password valid') try: - jf.authenticate(config['jellyfin']['username'], - config['jellyfin']['password']) - user = jf.newUser(data['username'], data['password']) + user = jf.newUser(data['username'], + data['password']) except Jellyfin.UserExistsError: - error = 'User already exists with name ' - error += data['username'] + error = f'User already exists named {data["username"]}' log.debug(error) return jsonify({'error': error}) except: @@ -138,36 +124,31 @@ def newUser(): checkInvite(data['code'], delete=True) if user.status_code == 200: try: - with open(config['files']['user_template'], 'r') as f: - default_policy = json.load(f) - jf.setPolicy(user.json()['Id'], default_policy) + policy = data_store.user_template + if policy != {}: + jf.setPolicy(user.json()['Id'], policy) + else: + log.debug('user policy was blank') except: - log.error('Failed to set new user policy. ' + - 'Ignore if you didn\'t create a template') + log.error('Failed to set new user policy') try: - with open(config['files']['user_configuration'], 'r') as f: - default_configuration = json.load(f) - with open(config['files']['user_displayprefs'], 'r') as f: - default_displayprefs = json.load(f) - if jf.setConfiguration(user.json()['Id'], - default_configuration): - jf.setDisplayPreferences(user.json()['Id'], - default_displayprefs) - log.debug('Set homescreen layout.') + configuration = data_store.user_configuration + displayprefs = data_store.user_displayprefs + if configuration != {} and displayprefs != {}: + if jf.setConfiguration(user.json()['Id'], + configuration): + jf.setDisplayPreferences(user.json()['Id'], + displayprefs) + log.debug('Set homescreen layout.') + else: + log.debug('user configuration and/or ' + + 'displayprefs were blank') except: - log.error('Failed to set new user homescreen kayout.' + - 'Ignore if you didn\'t create a template') + log.error('Failed to set new user homescreen layout') if config.getboolean('password_resets', 'enabled'): - try: - with open(config['files']['emails'], 'r') as f: - emails = json.load(f) - except (FileNotFoundError, json.decoder.JSONDecodeError): - emails = {} - emails[user.json()['Id']] = data['email'] - with open(config['files']['emails'], 'w') as f: - f.write(json.dumps(emails, indent=4)) + data_store.emails[user.json()['Id']] = data['email'] log.debug('Email address stored') - log.info('New User created.') + log.info('New user created') else: log.error(f'New user creation failed: {user.status_code}') return resp(False) @@ -179,15 +160,16 @@ def newUser(): return resp(False, code=401) -@app.route('/generateInvite', methods=['GET', 'POST']) +@app.route('/generateInvite', methods=['POST']) @auth.login_required def generateInvite(): current_time = datetime.datetime.now() data = request.get_json() delta = datetime.timedelta(hours=int(data['hours']), minutes=int(data['minutes'])) - invite = {'code': secrets.token_urlsafe(16)} - log.debug(f'Creating new invite: {invite["code"]}') + invite_code = secrets.token_urlsafe(16) + invite = {} + log.debug(f'Creating new invite: {invite_code}') valid_till = current_time + delta invite['valid_till'] = valid_till.strftime('%Y-%m-%dT%H:%M:%S.%f') if 'email' in data and config.getboolean('invite_emails', 'enabled'): @@ -202,19 +184,12 @@ def generateInvite(): from jellyfin_accounts.email import Smtp email = Smtp(address) email.construct_invite({'expiry': valid_till, - 'code': invite['code']}) + 'code': invite_code}) response = email.send() if response is False or type(response) != bool: invite['email'] = f'Failed to send to {address}' - try: - with open(config['files']['invites'], 'r') as f: - invites = json.load(f) - except (FileNotFoundError, json.decoder.JSONDecodeError): - invites = {'invites': []} - invites['invites'].append(invite) - with open(config['files']['invites'], 'w') as f: - f.write(json.dumps(invites, indent=4, default=str)) - log.info(f'New invite created: {invite["code"]}') + data_store.invites[invite_code] = invite + log.info(f'New invite created: {invite_code}') return resp() @@ -223,46 +198,30 @@ def generateInvite(): def getInvites(): log.debug('Invites requested') current_time = datetime.datetime.now() - try: - with open(config['files']['invites'], 'r') as f: - invites = json.load(f) - except (FileNotFoundError, json.decoder.JSONDecodeError): - invites = {'invites': []} + invites = dict(data_store.invites) + for code in invites: + checkInvite(code) + invites = dict(data_store.invites) response = {'invites': []} - for index, i in enumerate(invites['invites']): - expiry = datetime.datetime.strptime(i['valid_till'], + for code in invites: + expiry = datetime.datetime.strptime(invites[code]['valid_till'], '%Y-%m-%dT%H:%M:%S.%f') - if current_time >= expiry: - log.debug(('Housekeeping: Deleting old invite ' + - invites['invites'][index]['code'])) - del invites['invites'][index] - else: - valid_for = expiry - current_time - invite = {'code': i['code'], - 'hours': valid_for.seconds//3600, - 'minutes': (valid_for.seconds//60) % 60} - if 'email' in i: - invite['email'] = i['email'] - response['invites'].append(invite) - with open(config['files']['invites'], 'w') as f: - f.write(json.dumps(invites, indent=4, default=str)) + valid_for = expiry - current_time + invite = {'code': code, + 'hours': valid_for.seconds//3600, + 'minutes': (valid_for.seconds//60) % 60} + if 'email' in invites[code]: + invite['email'] = invites[code]['email'] + response['invites'].append(invite) return jsonify(response) - @app.route('/deleteInvite', methods=['POST']) @auth.login_required def deleteInvite(): code = request.get_json()['code'] - try: - with open(config['files']['invites'], 'r') as f: - invites = json.load(f) - except (FileNotFoundError, json.decoder.JSONDecodeError): - invites = {'invites': []} - for index, i in enumerate(invites['invites']): - if i['code'] == code: - del invites['invites'][index] - with open(config['files']['invites'], 'w') as f: - f.write(json.dumps(invites, indent=4, default=str)) + invites = dict(data_store.invites) + if code in invites: + del data_store.invites[code] log.info(f'Invite deleted: {code}') return resp() @@ -274,19 +233,13 @@ def get_token(): return jsonify({'token': token.decode('ascii')}) -@app.route('/getUsers', methods=['GET', 'POST']) +@app.route('/getUsers', methods=['GET']) @auth.login_required def getUsers(): log.debug('User and email list requested') - try: - with open(config['files']['emails'], 'r') as f: - emails = json.load(f) - except (FileNotFoundError, json.decoder.JSONDecodeError): - emails = {} response = {'users': []} - jf.authenticate(config['jellyfin']['username'], - config['jellyfin']['password']) users = jf.getUsers(public=False) + emails = data_store.emails for user in users: entry = {'name': user['Name']} if user['Id'] in emails: @@ -294,29 +247,17 @@ def getUsers(): response['users'].append(entry) return jsonify(response) + @app.route('/modifyUsers', methods=['POST']) @auth.login_required def modifyUsers(): data = request.get_json() - log.debug('User and email list modification requested') - try: - with open(config['files']['emails'], 'r') as f: - emails = json.load(f) - except (FileNotFoundError, json.decoder.JSONDecodeError): - emails = {} - jf.authenticate(config['jellyfin']['username'], - config['jellyfin']['password']) + log.debug('Email list modification requested') for key in data: uid = jf.getUsers(key, public=False)['Id'] + data_store.emails[uid] = data[key] log.debug(f'Email for user "{key}" modified') - emails[uid] = data[key] - try: - with open(config['files']['emails'], 'w') as f: - f.write(json.dumps(emails, indent=4)) - return resp() - except: - log.error('Could not store email') - return resp(success=False) + return resp() @app.route('/setDefaults', methods=['POST']) @@ -324,33 +265,27 @@ def modifyUsers(): def setDefaults(): data = request.get_json() username = data['username'] - log.debug(f'storing default settings from user {username}') - jf.authenticate(config['jellyfin']['username'], - config['jellyfin']['password']) + log.debug(f'Storing default settings from user {username}') try: user = jf.getUsers(username=username, public=False) except Jellyfin.UserNotFoundError: - log.error(f'couldn\'t find user {username}') - return resp(success=False) + log.error(f'Storing defaults failed: Couldn\'t find user {username}') + return resp(False) uid = user['Id'] policy = user['Policy'] - try: - with open(config['files']['user_template'], 'w') as f: - f.write(json.dumps(policy, indent=4)) - except: - log.error('Could not store user template') - return resp(success=False) + data_store.user_template = policy if data['homescreen']: configuration = user['Configuration'] try: - display_prefs = jf.getDisplayPreferences(uid) - with open(config['files']['user_configuration'], 'w') as f: - f.write(json.dumps(configuration, indent=4)) - with open(config['files']['user_displayprefs'], 'w') as f: - f.write(json.dumps(display_prefs, indent=4)) + displayprefs = jf.getDisplayPreferences(uid) + data_store.user_configuration = configuration + data_store.user_displayprefs = displayprefs except: - log.error('Could not store homescreen layout') + log.error('Storing defaults failed: ' + + 'couldn\'t store homescreen layout') + return resp(False) return resp() - + import jellyfin_accounts.setup + diff --git a/jf-accounts b/jf-accounts index e3d07a4..5417933 100755 --- a/jf-accounts +++ b/jf-accounts @@ -7,8 +7,10 @@ import logging import threading import signal import sys +import json from pathlib import Path from flask import Flask, g +from jellyfin_accounts.data_store import JSONStorage parser = argparse.ArgumentParser(description="jellyfin-accounts") @@ -97,6 +99,25 @@ for key in ['user_configuration', 'user_displayprefs']: log.debug(f'Using default {key}') config['files'][key] = str(data_dir / (key + '.json')) +with open(config['files']['invites'], 'r') as f: + temp_invites = json.load(f) +if 'invites' in temp_invites: + new_invites = {} + log.info('Converting invites.json to new format, temporary.') + for el in temp_invites['invites']: + i = {'valid_till': el['valid_till']} + if 'email' in el: + i['email'] = el['email'] + new_invites[el['code']] = i + with open(config['files']['invites'], 'w') as f: + f.write(json.dumps(new_invites, indent=4, default=str)) + + +data_store = JSONStorage(config['files']['emails'], + config['files']['invites'], + config['files']['user_template'], + config['files']['user_displayprefs'], + config['files']['user_configuration']) def default_css(): css = {} @@ -148,10 +169,10 @@ if args.get_defaults: import json from jellyfin_accounts.jf_api import Jellyfin jf = Jellyfin(config['jellyfin']['server'], - config['jellyfin']['client'], - config['jellyfin']['version'], - config['jellyfin']['device'], - config['jellyfin']['device_id']) + config['jellyfin']['client'], + config['jellyfin']['version'], + config['jellyfin']['device'], + config['jellyfin']['device_id']) print("NOTE: This can now be done through the web ui.") print(""" This tool lets you grab various settings from a user, @@ -187,8 +208,7 @@ if args.get_defaults: success = True except (ValueError, IndexError): pass - with open(config['files']['user_template'], 'w') as f: - f.write(json.dumps(policy, indent=4)) + data_store.user_template = policy print(f'Policy written to "{config["files"]["user_template"]}".') print('In future, this policy will be copied to all new users.') print('Step 2: Homescreen Layout') @@ -203,11 +223,9 @@ if args.get_defaults: user_id = users[user_index]['Id'] configuration = users[user_index]['Configuration'] display_prefs = jf.getDisplayPreferences(user_id) - with open(config['files']['user_configuration'], 'w') as f: - f.write(json.dumps(configuration, indent=4)) + data_store.user_configuration = configuration print(f'Configuration written to "{config["files"]["user_configuration"]}".') - with open(config['files']['user_displayprefs'], 'w') as f: - f.write(json.dumps(display_prefs, indent=4)) + data_store.user_displayprefs = display_prefs print(f'Display Prefs written to "{config["files"]["user_displayprefs"]}".') success = True elif choice.lower() == 'n': From bfbaca4094a4bb2235fe5a598f8efda2ac0713e4 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Tue, 16 Jun 2020 20:07:47 +0100 Subject: [PATCH 2/2] Move to Poetry for deps and packaging setup.py has been removed, and Poetry is now used to package for install. This fixed an issue i had with uploading to PyPI, so one can now run 'pip install jellyfin-accounts' to install. --- jellyfin_accounts/__init__.py | 275 +++++++++++ .../data}/config-default.ini | 0 {data => jellyfin_accounts/data}/email.html | 0 {data => jellyfin_accounts/data}/email.mjml | 0 {data => jellyfin_accounts/data}/email.txt | 0 .../data}/invite-email.html | 0 .../data}/invite-email.mjml | 0 .../data}/invite-email.txt | 0 .../data}/static/admin.js | 0 .../data}/static/android-chrome-192x192.png | Bin .../data}/static/android-chrome-512x512.png | Bin .../data}/static/apple-touch-icon.png | Bin .../data/static/bootstrap-jf.css | 9 + .../data}/static/browserconfig.xml | 0 .../data}/static/favicon-16x16.png | Bin .../data}/static/favicon-32x32.png | Bin .../data}/static/favicon.ico | Bin .../data}/static/mstile-150x150.png | Bin .../data}/static/safari-pinned-tab.svg | 0 .../data}/static/setup.js | 0 .../data}/static/site.webmanifest | 0 .../data}/templates/404.html | 0 .../data}/templates/admin.html | 0 .../data}/templates/form.html | 0 .../data}/templates/invalidCode.html | 0 .../data}/templates/setup.html | 0 jellyfin_accounts/email.py | 4 +- jellyfin_accounts/login.py | 4 +- jellyfin_accounts/pw_reset.py | 4 +- jellyfin_accounts/setup.py | 4 +- jellyfin_accounts/web.py | 4 +- jellyfin_accounts/web_api.py | 4 +- jf-accounts | 271 ----------- poetry.lock | 449 ++++++++++++++++++ pyproject.toml | 42 ++ requirements.txt | 12 - setup.py | 62 --- 37 files changed, 787 insertions(+), 357 deletions(-) mode change 100644 => 100755 jellyfin_accounts/__init__.py rename {data => jellyfin_accounts/data}/config-default.ini (100%) rename {data => jellyfin_accounts/data}/email.html (100%) rename {data => jellyfin_accounts/data}/email.mjml (100%) rename {data => jellyfin_accounts/data}/email.txt (100%) rename {data => jellyfin_accounts/data}/invite-email.html (100%) rename {data => jellyfin_accounts/data}/invite-email.mjml (100%) rename {data => jellyfin_accounts/data}/invite-email.txt (100%) rename {data => jellyfin_accounts/data}/static/admin.js (100%) rename {data => jellyfin_accounts/data}/static/android-chrome-192x192.png (100%) rename {data => jellyfin_accounts/data}/static/android-chrome-512x512.png (100%) rename {data => jellyfin_accounts/data}/static/apple-touch-icon.png (100%) create mode 100644 jellyfin_accounts/data/static/bootstrap-jf.css rename {data => jellyfin_accounts/data}/static/browserconfig.xml (100%) rename {data => jellyfin_accounts/data}/static/favicon-16x16.png (100%) rename {data => jellyfin_accounts/data}/static/favicon-32x32.png (100%) rename {data => jellyfin_accounts/data}/static/favicon.ico (100%) rename {data => jellyfin_accounts/data}/static/mstile-150x150.png (100%) rename {data => jellyfin_accounts/data}/static/safari-pinned-tab.svg (100%) rename {data => jellyfin_accounts/data}/static/setup.js (100%) rename {data => jellyfin_accounts/data}/static/site.webmanifest (100%) rename {data => jellyfin_accounts/data}/templates/404.html (100%) rename {data => jellyfin_accounts/data}/templates/admin.html (100%) rename {data => jellyfin_accounts/data}/templates/form.html (100%) rename {data => jellyfin_accounts/data}/templates/invalidCode.html (100%) rename {data => jellyfin_accounts/data}/templates/setup.html (100%) delete mode 100755 jf-accounts create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/jellyfin_accounts/__init__.py b/jellyfin_accounts/__init__.py old mode 100644 new mode 100755 index e69de29..6e6af33 --- a/jellyfin_accounts/__init__.py +++ b/jellyfin_accounts/__init__.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +__version__ = "0.2" + +import secrets +import configparser +import shutil +import argparse +import logging +import threading +import signal +import sys +import json +from pathlib import Path +from flask import Flask, g +from jellyfin_accounts.data_store import JSONStorage +parser = argparse.ArgumentParser(description="jellyfin-accounts") + +parser.add_argument("-c", "--config", + help="specifies path to configuration file.") +parser.add_argument("-d", "--data", + help=("specifies directory to store data in. " + + "defaults to ~/.jf-accounts.")) +parser.add_argument("--host", + help="address to host web ui on.") +parser.add_argument("-p", "--port", + help="port to host web ui on.") +parser.add_argument("-g", "--get_defaults", + help=("tool to grab a JF users " + + "policy (access, perms, etc.) and " + + "homescreen layout and " + + "output it as json to be used as a user template."), + action='store_true') + +args, leftovers = parser.parse_known_args() + +if args.data is not None: + data_dir = Path(args.data) +else: + data_dir = Path.home() / '.jf-accounts' + +local_dir = (Path(__file__).parent / 'data').resolve() + +first_run = False +if data_dir.exists() is False or (data_dir / 'config.ini').exists() is False: + if not data_dir.exists(): + Path.mkdir(data_dir) + print(f'Config dir not found, so created at {str(data_dir)}') + if args.config is None: + config_path = data_dir / 'config.ini' + shutil.copy(str(local_dir / 'config-default.ini'), + str(config_path)) + print("Setup through the web UI, or quit and edit the configuration manually.") + first_run = True + else: + config_path = Path(args.config) + print(f'config.ini can be found at {str(config_path)}') +else: + config_path = data_dir / 'config.ini' + +config = configparser.RawConfigParser() +config.read(config_path) + +def create_log(name): + log = logging.getLogger(name) + handler = logging.StreamHandler(sys.stdout) + if config.getboolean('ui', 'debug'): + log.setLevel(logging.DEBUG) + handler.setLevel(logging.DEBUG) + else: + log.setLevel(logging.INFO) + handler.setLevel(logging.INFO) + fmt = ' %(name)s - %(levelname)s - %(message)s' + format = logging.Formatter(fmt) + handler.setFormatter(format) + log.addHandler(handler) + log.propagate = False + return log + +log = create_log('main') +web_log = create_log('waitress') +if not first_run: + email_log = create_log('emails') + auth_log = create_log('auth') + +if args.host is not None: + log.debug(f'Using specified host {args.host}') + config['ui']['host'] = args.host +if args.port is not None: + log.debug(f'Using specified port {args.port}') + config['ui']['port'] = args.port + +for key in config['files']: + if config['files'][key] == '': + if key != 'custom_css': + log.debug(f'Using default {key}') + config['files'][key] = str(data_dir / (key + '.json')) + +for key in ['user_configuration', 'user_displayprefs']: + if key not in config['files']: + log.debug(f'Using default {key}') + config['files'][key] = str(data_dir / (key + '.json')) + +with open(config['files']['invites'], 'r') as f: + temp_invites = json.load(f) +if 'invites' in temp_invites: + new_invites = {} + log.info('Converting invites.json to new format, temporary.') + for el in temp_invites['invites']: + i = {'valid_till': el['valid_till']} + if 'email' in el: + i['email'] = el['email'] + new_invites[el['code']] = i + with open(config['files']['invites'], 'w') as f: + f.write(json.dumps(new_invites, indent=4, default=str)) + + +data_store = JSONStorage(config['files']['emails'], + config['files']['invites'], + config['files']['user_template'], + config['files']['user_displayprefs'], + config['files']['user_configuration']) + + +def default_css(): + css = {} + css['href'] = "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" + css['integrity'] = "sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" + css['crossorigin'] = "anonymous" + return css + + +css = {} +css = default_css() +if 'custom_css' in config['files']: + if config['files']['custom_css'] != '': + try: + shutil.copy(config['files']['custom_css'], + (local_dir / 'static' / 'bootstrap.css')) + log.debug('Loaded custom CSS') + css['href'] = '/bootstrap.css' + css['integrity'] = '' + css['crossorigin'] = '' + except FileNotFoundError: + log.error(f'Custom CSS {config["files"]["custom_css"]} not found, using default.') + + +if ('email_html' not in config['password_resets'] or + config['password_resets']['email_html'] == ''): + log.debug('Using default password reset email HTML template') + config['password_resets']['email_html'] = str(local_dir / 'email.html') +if ('email_text' not in config['password_resets'] or + config['password_resets']['email_text'] == ''): + log.debug('Using default password reset email plaintext template') + config['password_resets']['email_text'] = str(local_dir / 'email.txt') + +if ('email_html' not in config['invite_emails'] or + config['invite_emails']['email_html'] == ''): + log.debug('Using default invite email HTML template') + config['invite_emails']['email_html'] = str(local_dir / + 'invite-email.html') +if ('email_text' not in config['invite_emails'] or + config['invite_emails']['email_text'] == ''): + log.debug('Using default invite email plaintext template') + config['invite_emails']['email_text'] = str(local_dir / + 'invite-email.txt') +if ('public_server' not in config['jellyfin'] or + config['jellyfin']['public_server'] == ''): + config['jellyfin']['public_server'] = config['jellyfin']['server'] + + +def main(): + if args.get_defaults: + import json + from jellyfin_accounts.jf_api import Jellyfin + jf = Jellyfin(config['jellyfin']['server'], + config['jellyfin']['client'], + config['jellyfin']['version'], + config['jellyfin']['device'], + config['jellyfin']['device_id']) + print("NOTE: This can now be done through the web ui.") + print(""" + This tool lets you grab various settings from a user, + so that they can be applied every time a new account is + created. """) + print("Step 1: User Policy.") + print(""" + A user policy stores a users permissions (e.g access rights and + most of the other settings in the 'Profile' and 'Access' tabs + of a user). """) + success = False + msg = "Get public users only or all users? (requires auth) [public/all]: " + public = False + while not success: + choice = input(msg) + if choice == 'public': + public = True + print("Make sure the user is publicly visible!") + success = True + elif choice == 'all': + jf.authenticate(config['jellyfin']['username'], + config['jellyfin']['password']) + public = False + success = True + users = jf.getUsers(public=public) + for index, user in enumerate(users): + print(f'{index+1}) {user["Name"]}') + success = False + while not success: + try: + user_index = int(input(">: "))-1 + policy = users[user_index]['Policy'] + success = True + except (ValueError, IndexError): + pass + data_store.user_template = policy + print(f'Policy written to "{config["files"]["user_template"]}".') + print('In future, this policy will be copied to all new users.') + print('Step 2: Homescreen Layout') + print(""" + You may want to customize the default layout of a new user's + home screen. These settings can be applied to an account through + the 'Home' section in a user's settings. """) + success = False + while not success: + choice = input("Grab the chosen user's homescreen layout? [y/n]: ") + if choice.lower() == 'y': + user_id = users[user_index]['Id'] + configuration = users[user_index]['Configuration'] + display_prefs = jf.getDisplayPreferences(user_id) + data_store.user_configuration = configuration + print(f'Configuration written to "{config["files"]["user_configuration"]}".') + data_store.user_displayprefs = display_prefs + print(f'Display Prefs written to "{config["files"]["user_displayprefs"]}".') + success = True + elif choice.lower() == 'n': + success = True + + else: + def signal_handler(sig, frame): + print('Quitting...') + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + global app + app = Flask(__name__, root_path=str(local_dir)) + app.config['DEBUG'] = config.getboolean('ui', 'debug') + app.config['SECRET_KEY'] = secrets.token_urlsafe(16) + + from waitress import serve + if first_run: + import jellyfin_accounts.setup + host = config['ui']['host'] + port = config['ui']['port'] + log.info('Starting web UI for first run setup...') + serve(app, + host=host, + port=port) + else: + import jellyfin_accounts.web_api + import jellyfin_accounts.web + host = config['ui']['host'] + port = config['ui']['port'] + log.info(f'Starting web UI on {host}:{port}') + if config.getboolean('password_resets', 'enabled'): + def start_pwr(): + import jellyfin_accounts.pw_reset + jellyfin_accounts.pw_reset.start() + pwr = threading.Thread(target=start_pwr, daemon=True) + log.info('Starting email thread') + pwr.start() + + serve(app, + host=host, + port=int(port)) diff --git a/data/config-default.ini b/jellyfin_accounts/data/config-default.ini similarity index 100% rename from data/config-default.ini rename to jellyfin_accounts/data/config-default.ini diff --git a/data/email.html b/jellyfin_accounts/data/email.html similarity index 100% rename from data/email.html rename to jellyfin_accounts/data/email.html diff --git a/data/email.mjml b/jellyfin_accounts/data/email.mjml similarity index 100% rename from data/email.mjml rename to jellyfin_accounts/data/email.mjml diff --git a/data/email.txt b/jellyfin_accounts/data/email.txt similarity index 100% rename from data/email.txt rename to jellyfin_accounts/data/email.txt diff --git a/data/invite-email.html b/jellyfin_accounts/data/invite-email.html similarity index 100% rename from data/invite-email.html rename to jellyfin_accounts/data/invite-email.html diff --git a/data/invite-email.mjml b/jellyfin_accounts/data/invite-email.mjml similarity index 100% rename from data/invite-email.mjml rename to jellyfin_accounts/data/invite-email.mjml diff --git a/data/invite-email.txt b/jellyfin_accounts/data/invite-email.txt similarity index 100% rename from data/invite-email.txt rename to jellyfin_accounts/data/invite-email.txt diff --git a/data/static/admin.js b/jellyfin_accounts/data/static/admin.js similarity index 100% rename from data/static/admin.js rename to jellyfin_accounts/data/static/admin.js diff --git a/data/static/android-chrome-192x192.png b/jellyfin_accounts/data/static/android-chrome-192x192.png similarity index 100% rename from data/static/android-chrome-192x192.png rename to jellyfin_accounts/data/static/android-chrome-192x192.png diff --git a/data/static/android-chrome-512x512.png b/jellyfin_accounts/data/static/android-chrome-512x512.png similarity index 100% rename from data/static/android-chrome-512x512.png rename to jellyfin_accounts/data/static/android-chrome-512x512.png diff --git a/data/static/apple-touch-icon.png b/jellyfin_accounts/data/static/apple-touch-icon.png similarity index 100% rename from data/static/apple-touch-icon.png rename to jellyfin_accounts/data/static/apple-touch-icon.png diff --git a/jellyfin_accounts/data/static/bootstrap-jf.css b/jellyfin_accounts/data/static/bootstrap-jf.css new file mode 100644 index 0000000..ab6736b --- /dev/null +++ b/jellyfin_accounts/data/static/bootstrap-jf.css @@ -0,0 +1,9 @@ +/* +* Orson http://en.orson.io , autreplanete http://www.autreplanete.com/ +* +**//*! + * Bootstrap v4.0.0-beta (https://getbootstrap.com) + * Copyright 2011-2017 The Bootstrap Authors + * Copyright 2011-2017 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */@media print {*,*::before,*::after {text-shadow: none !important;box-shadow: none !important; }a,a:visited {text-decoration: underline; }abbr[title]::after {content: " (" attr(title) ")"; }pre {white-space: pre-wrap !important; }pre,blockquote {border: 1px solid #999;page-break-inside: avoid; }thead {display: table-header-group; }tr,img {page-break-inside: avoid; }p,h2,h3 {orphans: 3;widows: 3; }h2,h3 {page-break-after: avoid; }.navbar {display: none; }.badge {border: 1px solid #000; }.table {border-collapse: collapse !important; }.table td,.table th {background-color: #fff !important; }.table-bordered th,.table-bordered td {border: 1px solid #ddd !important; } }html {box-sizing: border-box;font-family: sans-serif;line-height: 1.15;-webkit-text-size-adjust: 100%;-ms-text-size-adjust: 100%;-ms-overflow-style: scrollbar;-webkit-tap-highlight-color: transparent; }*,*::before,*::after {box-sizing: inherit; }@-ms-viewport {width: device-width; }article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {display: block; }body {margin: 0;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-size: 1rem;font-weight: normal;line-height: 1.5;color: rgba(255, 255, 255, 0.8);background-color: #101010; }[tabindex="-1"]:focus {outline: none !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[title],abbr[data-original-title] {text-decoration: underline;text-decoration: underline dotted;cursor: help;border-bottom: 0; }address {margin-bottom: 1rem;font-style: normal;line-height: inherit; }ol,ul,dl {margin-top: 0;margin-bottom: 1rem; }ol ol,ul ul,ol ul,ul ol {margin-bottom: 0; }dt {font-weight: bold; }dd {margin-bottom: .5rem;margin-left: 0; }blockquote {margin: 0 0 1rem; }dfn {font-style: italic; }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: #00a4dc;text-decoration: none;background-color: transparent;-webkit-text-decoration-skip: objects; }a:hover {color: #006b90;text-decoration: underline; }a:not([href]):not([tabindex]) {color: inherit;text-decoration: none; }a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {color: inherit;text-decoration: none; }a:not([href]):not([tabindex]):focus {outline: 0; }pre,code,kbd,samp {font-family: monospace, monospace;font-size: 1em; }pre {margin-top: 0;margin-bottom: 1rem;overflow: auto; }figure {margin: 0 0 1rem; }img {vertical-align: middle;border-style: none; }svg:not(:root) {overflow: hidden; }a,area,button,[role="button"],input,label,select,summary,textarea {touch-action: manipulation; }table {border-collapse: collapse; }caption {padding-top: 0.75rem;padding-bottom: 0.75rem;color: #303030;text-align: left;caption-side: bottom; }th {text-align: left; }label {display: inline-block;margin-bottom: .5rem; }button:focus {outline: 1px dotted;outline: 5px auto -webkit-focus-ring-color; }input,button,select,optgroup,textarea {margin: 0;font-family: inherit;font-size: inherit;line-height: inherit; }button,input {overflow: visible; }button,select {text-transform: none; }button,html [type="button"],[type="reset"],[type="submit"] {-webkit-appearance: button; }button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner {padding: 0;border-style: none; }input[type="radio"],input[type="checkbox"] {box-sizing: border-box;padding: 0; }input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"] {-webkit-appearance: listbox; }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-cancel-button,[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; }template {display: none; }[hidden] {display: none !important; }h1, h2, h3, h4, h5, h6,.h1, .h2, .h3, .h4, .h5, .h6 {margin-bottom: 0.5rem;font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit; }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.1; }.display-2 {font-size: 5.5rem;font-weight: 300;line-height: 1.1; }.display-3 {font-size: 4.5rem;font-weight: 300;line-height: 1.1; }.display-4 {font-size: 3.5rem;font-weight: 300;line-height: 1.1; }hr {margin-top: 1rem;margin-bottom: 1rem;border: 0;border-top: 1px solid rgba(0, 0, 0, 0.1); }small,.small {font-size: 80%;font-weight: normal; }mark,.mark {padding: 0.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: 5px; }.initialism {font-size: 90%;text-transform: uppercase; }.blockquote {margin-bottom: 1rem;font-size: 1.25rem; }.blockquote-footer {display: block;font-size: 80%;color: #303030; }.blockquote-footer::before {content: "\2014 \00A0"; }.img-fluid {max-width: 100%;height: auto; }.img-thumbnail {padding: 0.25rem;background-color: #101010;border: 1px solid #ddd;border-radius: 0.25rem;transition: all 0.2s ease-in-out;max-width: 100%;height: auto; }.figure {display: inline-block; }.figure-img {margin-bottom: 0.5rem;line-height: 1; }.figure-caption {font-size: 90%;color: #303030; }code,kbd,pre,samp {font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }code {padding: 0.2rem 0.4rem;font-size: 90%;color: #bd4147;background-color: #f8f9fa;border-radius: 0.25rem; }a > code {padding: 0;color: inherit;background-color: inherit; }kbd {padding: 0.2rem 0.4rem;font-size: 90%;color: #fff;background-color: #212529;border-radius: 0.2rem; }kbd kbd {padding: 0;font-size: 100%;font-weight: bold; }pre {display: block;margin-top: 0;margin-bottom: 1rem;font-size: 90%;color: #212529; }pre code {padding: 0;font-size: inherit;color: inherit;background-color: transparent;border-radius: 0; }.pre-scrollable {max-height: 340px;overflow-y: scroll; }.container {margin-right: auto;margin-left: auto;padding-right: 15px;padding-left: 15px;width: 100%; }@media (min-width: 576px) {.container {max-width: 540px; } }@media (min-width: 768px) {.container {max-width: 720px; } }@media (min-width: 992px) {.container {max-width: 960px; } }@media (min-width: 1200px) {.container {max-width: 1140px; } }.container-fluid {width: 100%;margin-right: auto;margin-left: auto;padding-right: 15px;padding-left: 15px;width: 100%; }.row {display: flex;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-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,.col-auto, .col-sm-1, .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-10, .col-sm-11, .col-sm-12, .col-sm,.col-sm-auto, .col-md-1, .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-10, .col-md-11, .col-md-12, .col-md,.col-md-auto, .col-lg-1, .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-10, .col-lg-11, .col-lg-12, .col-lg,.col-lg-auto, .col-xl-1, .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-10, .col-xl-11, .col-xl-12, .col-xl,.col-xl-auto {position: relative;width: 100%;min-height: 1px;padding-right: 15px;padding-left: 15px; }.col {flex-basis: 0;flex-grow: 1;max-width: 100%; }.col-auto {flex: 0 0 auto;width: auto;max-width: none; }.col-1 {flex: 0 0 8.33333%;max-width: 8.33333%; }.col-2 {flex: 0 0 16.66667%;max-width: 16.66667%; }.col-3 {flex: 0 0 25%;max-width: 25%; }.col-4 {flex: 0 0 33.33333%;max-width: 33.33333%; }.col-5 {flex: 0 0 41.66667%;max-width: 41.66667%; }.col-6 {flex: 0 0 50%;max-width: 50%; }.col-7 {flex: 0 0 58.33333%;max-width: 58.33333%; }.col-8 {flex: 0 0 66.66667%;max-width: 66.66667%; }.col-9 {flex: 0 0 75%;max-width: 75%; }.col-10 {flex: 0 0 83.33333%;max-width: 83.33333%; }.col-11 {flex: 0 0 91.66667%;max-width: 91.66667%; }.col-12 {flex: 0 0 100%;max-width: 100%; }.order-1 {order: 1; }.order-2 {order: 2; }.order-3 {order: 3; }.order-4 {order: 4; }.order-5 {order: 5; }.order-6 {order: 6; }.order-7 {order: 7; }.order-8 {order: 8; }.order-9 {order: 9; }.order-10 {order: 10; }.order-11 {order: 11; }.order-12 {order: 12; }@media (min-width: 576px) {.col-sm {flex-basis: 0;flex-grow: 1;max-width: 100%; }.col-sm-auto {flex: 0 0 auto;width: auto;max-width: none; }.col-sm-1 {flex: 0 0 8.33333%;max-width: 8.33333%; }.col-sm-2 {flex: 0 0 16.66667%;max-width: 16.66667%; }.col-sm-3 {flex: 0 0 25%;max-width: 25%; }.col-sm-4 {flex: 0 0 33.33333%;max-width: 33.33333%; }.col-sm-5 {flex: 0 0 41.66667%;max-width: 41.66667%; }.col-sm-6 {flex: 0 0 50%;max-width: 50%; }.col-sm-7 {flex: 0 0 58.33333%;max-width: 58.33333%; }.col-sm-8 {flex: 0 0 66.66667%;max-width: 66.66667%; }.col-sm-9 {flex: 0 0 75%;max-width: 75%; }.col-sm-10 {flex: 0 0 83.33333%;max-width: 83.33333%; }.col-sm-11 {flex: 0 0 91.66667%;max-width: 91.66667%; }.col-sm-12 {flex: 0 0 100%;max-width: 100%; }.order-sm-1 {order: 1; }.order-sm-2 {order: 2; }.order-sm-3 {order: 3; }.order-sm-4 {order: 4; }.order-sm-5 {order: 5; }.order-sm-6 {order: 6; }.order-sm-7 {order: 7; }.order-sm-8 {order: 8; }.order-sm-9 {order: 9; }.order-sm-10 {order: 10; }.order-sm-11 {order: 11; }.order-sm-12 {order: 12; } }@media (min-width: 768px) {.col-md {flex-basis: 0;flex-grow: 1;max-width: 100%; }.col-md-auto {flex: 0 0 auto;width: auto;max-width: none; }.col-md-1 {flex: 0 0 8.33333%;max-width: 8.33333%; }.col-md-2 {flex: 0 0 16.66667%;max-width: 16.66667%; }.col-md-3 {flex: 0 0 25%;max-width: 25%; }.col-md-4 {flex: 0 0 33.33333%;max-width: 33.33333%; }.col-md-5 {flex: 0 0 41.66667%;max-width: 41.66667%; }.col-md-6 {flex: 0 0 50%;max-width: 50%; }.col-md-7 {flex: 0 0 58.33333%;max-width: 58.33333%; }.col-md-8 {flex: 0 0 66.66667%;max-width: 66.66667%; }.col-md-9 {flex: 0 0 75%;max-width: 75%; }.col-md-10 {flex: 0 0 83.33333%;max-width: 83.33333%; }.col-md-11 {flex: 0 0 91.66667%;max-width: 91.66667%; }.col-md-12 {flex: 0 0 100%;max-width: 100%; }.order-md-1 {order: 1; }.order-md-2 {order: 2; }.order-md-3 {order: 3; }.order-md-4 {order: 4; }.order-md-5 {order: 5; }.order-md-6 {order: 6; }.order-md-7 {order: 7; }.order-md-8 {order: 8; }.order-md-9 {order: 9; }.order-md-10 {order: 10; }.order-md-11 {order: 11; }.order-md-12 {order: 12; } }@media (min-width: 992px) {.col-lg {flex-basis: 0;flex-grow: 1;max-width: 100%; }.col-lg-auto {flex: 0 0 auto;width: auto;max-width: none; }.col-lg-1 {flex: 0 0 8.33333%;max-width: 8.33333%; }.col-lg-2 {flex: 0 0 16.66667%;max-width: 16.66667%; }.col-lg-3 {flex: 0 0 25%;max-width: 25%; }.col-lg-4 {flex: 0 0 33.33333%;max-width: 33.33333%; }.col-lg-5 {flex: 0 0 41.66667%;max-width: 41.66667%; }.col-lg-6 {flex: 0 0 50%;max-width: 50%; }.col-lg-7 {flex: 0 0 58.33333%;max-width: 58.33333%; }.col-lg-8 {flex: 0 0 66.66667%;max-width: 66.66667%; }.col-lg-9 {flex: 0 0 75%;max-width: 75%; }.col-lg-10 {flex: 0 0 83.33333%;max-width: 83.33333%; }.col-lg-11 {flex: 0 0 91.66667%;max-width: 91.66667%; }.col-lg-12 {flex: 0 0 100%;max-width: 100%; }.order-lg-1 {order: 1; }.order-lg-2 {order: 2; }.order-lg-3 {order: 3; }.order-lg-4 {order: 4; }.order-lg-5 {order: 5; }.order-lg-6 {order: 6; }.order-lg-7 {order: 7; }.order-lg-8 {order: 8; }.order-lg-9 {order: 9; }.order-lg-10 {order: 10; }.order-lg-11 {order: 11; }.order-lg-12 {order: 12; } }@media (min-width: 1200px) {.col-xl {flex-basis: 0;flex-grow: 1;max-width: 100%; }.col-xl-auto {flex: 0 0 auto;width: auto;max-width: none; }.col-xl-1 {flex: 0 0 8.33333%;max-width: 8.33333%; }.col-xl-2 {flex: 0 0 16.66667%;max-width: 16.66667%; }.col-xl-3 {flex: 0 0 25%;max-width: 25%; }.col-xl-4 {flex: 0 0 33.33333%;max-width: 33.33333%; }.col-xl-5 {flex: 0 0 41.66667%;max-width: 41.66667%; }.col-xl-6 {flex: 0 0 50%;max-width: 50%; }.col-xl-7 {flex: 0 0 58.33333%;max-width: 58.33333%; }.col-xl-8 {flex: 0 0 66.66667%;max-width: 66.66667%; }.col-xl-9 {flex: 0 0 75%;max-width: 75%; }.col-xl-10 {flex: 0 0 83.33333%;max-width: 83.33333%; }.col-xl-11 {flex: 0 0 91.66667%;max-width: 91.66667%; }.col-xl-12 {flex: 0 0 100%;max-width: 100%; }.order-xl-1 {order: 1; }.order-xl-2 {order: 2; }.order-xl-3 {order: 3; }.order-xl-4 {order: 4; }.order-xl-5 {order: 5; }.order-xl-6 {order: 6; }.order-xl-7 {order: 7; }.order-xl-8 {order: 8; }.order-xl-9 {order: 9; }.order-xl-10 {order: 10; }.order-xl-11 {order: 11; }.order-xl-12 {order: 12; } }.table {width: 100%;max-width: 100%;margin-bottom: 1rem;background-color: transparent; }.table th,.table td {padding: 0.75rem;vertical-align: top;border-top: 1px solid #e9ecef; }.table thead th {vertical-align: bottom;border-bottom: 2px solid #e9ecef; }.table tbody + tbody {border-top: 2px solid #e9ecef; }.table .table {background-color: #101010; }.table-sm th,.table-sm td {padding: 0.3rem; }.table-bordered {border: 1px solid #e9ecef; }.table-bordered th,.table-bordered td {border: 1px solid #e9ecef; }.table-bordered thead th,.table-bordered thead td {border-bottom-width: 2px; }.table-striped tbody tr:nth-of-type(odd) {background-color: rgba(0, 0, 0, 0.05); }.table-hover tbody tr:hover {background-color: rgba(0, 0, 0, 0.075); }.table-primary,.table-primary > th,.table-primary > td {background-color: #b8e6f5; }.table-hover .table-primary:hover {background-color: #a2def2; }.table-hover .table-primary:hover > td,.table-hover .table-primary:hover > th {background-color: #a2def2; }.table-secondary,.table-secondary > th,.table-secondary > td {background-color: #c5c5c5; }.table-hover .table-secondary:hover {background-color: #b8b8b8; }.table-hover .table-secondary:hover > td,.table-hover .table-secondary:hover > th {background-color: #b8b8b8; }.table-success,.table-success > th,.table-success > td {background-color: #c3e6cb; }.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 > th,.table-info > td {background-color: #bee5eb; }.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 > th,.table-warning > td {background-color: #ffeeba; }.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 > th,.table-danger > td {background-color: #f5c6cb; }.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 > th,.table-light > td {background-color: #fdfdfe; }.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 > th,.table-dark > td {background-color: #c6c8ca; }.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 > th,.table-active > td {background-color: rgba(0, 0, 0, 0.075); }.table-hover .table-active:hover {background-color: rgba(0, 0, 0, 0.075); }.table-hover .table-active:hover > td,.table-hover .table-active:hover > th {background-color: rgba(0, 0, 0, 0.075); }.thead-inverse th {color: #101010;background-color: #212529; }.thead-default th {color: #495057;background-color: #e9ecef; }.table-inverse {color: #101010;background-color: #212529; }.table-inverse th,.table-inverse td,.table-inverse thead th {border-color: #32383e; }.table-inverse.table-bordered {border: 0; }.table-inverse.table-striped tbody tr:nth-of-type(odd) {background-color: rgba(255, 255, 255, 0.05); }.table-inverse.table-hover tbody tr:hover {background-color: rgba(255, 255, 255, 0.075); }@media (max-width: 991px) {.table-responsive {display: block;width: 100%;overflow-x: auto;-ms-overflow-style: -ms-autohiding-scrollbar; }.table-responsive.table-bordered {border: 0; } }.form-control {display: block;width: 100%;padding: 0.5rem 0.75rem;font-size: 1rem;line-height: 1.25;color: #495057;background-color: #fff;background-image: none;background-clip: padding-box;border: 1px solid rgba(0, 0, 0, 0.15);border-radius: 0.25rem;transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; }.form-control::-ms-expand {background-color: transparent;border: 0; }.form-control:focus {color: #495057;background-color: #fff;border-color: #5dd6ff;outline: none; }.form-control::placeholder {color: #303030;opacity: 1; }.form-control:disabled, .form-control[readonly] {background-color: #e9ecef;opacity: 1; }select.form-control:not([size]):not([multiple]) {height: calc(2.25rem + 2px); }select.form-control:focus::-ms-value {color: #495057;background-color: #fff; }.form-control-file,.form-control-range {display: block; }.col-form-label {padding-top: calc(0.5rem - 1px * 2);padding-bottom: calc(0.5rem - 1px * 2);margin-bottom: 0; }.col-form-label-lg {padding-top: calc(0.5rem - 1px * 2);padding-bottom: calc(0.5rem - 1px * 2);font-size: 1.25rem; }.col-form-label-sm {padding-top: calc(0.25rem - 1px * 2);padding-bottom: calc(0.25rem - 1px * 2);font-size: 0.875rem; }.col-form-legend {padding-top: 0.5rem;padding-bottom: 0.5rem;margin-bottom: 0;font-size: 1rem; }.form-control-plaintext {padding-top: 0.5rem;padding-bottom: 0.5rem;margin-bottom: 0;line-height: 1.25;border: solid transparent;border-width: 1px 0; }.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control,.input-group-sm > .form-control-plaintext.input-group-addon,.input-group-sm > .input-group-btn > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control,.input-group-lg > .form-control-plaintext.input-group-addon,.input-group-lg > .input-group-btn > .form-control-plaintext.btn {padding-right: 0;padding-left: 0; }.form-control-sm, .input-group-sm > .form-control,.input-group-sm > .input-group-addon,.input-group-sm > .input-group-btn > .btn {padding: 0.25rem 0.5rem;font-size: 0.875rem;line-height: 1.5;border-radius: 0.2rem; }select.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]),.input-group-sm > select.input-group-addon:not([size]):not([multiple]),.input-group-sm > .input-group-btn > select.btn:not([size]):not([multiple]) {height: calc(1.8125rem + 2px); }.form-control-lg, .input-group-lg > .form-control,.input-group-lg > .input-group-addon,.input-group-lg > .input-group-btn > .btn {padding: 0.5rem 1rem;font-size: 1.25rem;line-height: 1.5;border-radius: 0.3rem; }select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]),.input-group-lg > select.input-group-addon:not([size]):not([multiple]),.input-group-lg > .input-group-btn > select.btn:not([size]):not([multiple]) {height: calc(2.3125rem + 2px); }.form-group {margin-bottom: 1rem; }.form-text {display: block;margin-top: 0.25rem; }.form-row {display: flex;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;margin-bottom: 0.5rem; }.form-check.disabled .form-check-label {color: #303030; }.form-check-label {padding-left: 1.25rem;margin-bottom: 0; }.form-check-input {position: absolute;margin-top: 0.25rem;margin-left: -1.25rem; }.form-check-input:only-child {position: static; }.form-check-inline {display: inline-block; }.form-check-inline .form-check-label {vertical-align: middle; }.form-check-inline + .form-check-inline {margin-left: 0.75rem; }.invalid-feedback {display: none;margin-top: .25rem;font-size: .875rem;color: #dc3545; }.invalid-tooltip {position: absolute;top: 100%;z-index: 5;display: none;width: 250px;padding: .5rem;margin-top: .1rem;font-size: .875rem;line-height: 1;color: #fff;background-color: rgba(220, 53, 69, 0.8);border-radius: .2rem; }.was-validated .form-control:valid, .form-control.is-valid, .was-validated.custom-select:valid,.custom-select.is-valid {border-color: #28a745; }.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated.custom-select:valid:focus,.custom-select.is-valid:focus {box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); }.was-validated .form-control:valid ~ .invalid-feedback,.was-validated .form-control:valid ~ .invalid-tooltip, .form-control.is-valid ~ .invalid-feedback,.form-control.is-valid ~ .invalid-tooltip, .was-validated.custom-select:valid ~ .invalid-feedback,.was-validated.custom-select:valid ~ .invalid-tooltip,.custom-select.is-valid ~ .invalid-feedback,.custom-select.is-valid ~ .invalid-tooltip {display: block; }.was-validated .form-check-input:valid + .form-check-label, .form-check-input.is-valid + .form-check-label {color: #28a745; }.was-validated .custom-control-input:valid ~ .custom-control-indicator, .custom-control-input.is-valid ~ .custom-control-indicator {background-color: rgba(40, 167, 69, 0.25); }.was-validated .custom-control-input:valid ~ .custom-control-description, .custom-control-input.is-valid ~ .custom-control-description {color: #28a745; }.was-validated .custom-file-input:valid ~ .custom-file-control, .custom-file-input.is-valid ~ .custom-file-control {border-color: #28a745; }.was-validated .custom-file-input:valid ~ .custom-file-control::before, .custom-file-input.is-valid ~ .custom-file-control::before {border-color: inherit; }.was-validated .custom-file-input:valid:focus, .custom-file-input.is-valid:focus {box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); }.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated.custom-select:invalid,.custom-select.is-invalid {border-color: #dc3545; }.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated.custom-select:invalid:focus,.custom-select.is-invalid:focus {box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); }.was-validated .form-control:invalid ~ .invalid-feedback,.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,.form-control.is-invalid ~ .invalid-tooltip, .was-validated.custom-select:invalid ~ .invalid-feedback,.was-validated.custom-select:invalid ~ .invalid-tooltip,.custom-select.is-invalid ~ .invalid-feedback,.custom-select.is-invalid ~ .invalid-tooltip {display: block; }.was-validated .form-check-input:invalid + .form-check-label, .form-check-input.is-invalid + .form-check-label {color: #dc3545; }.was-validated .custom-control-input:invalid ~ .custom-control-indicator, .custom-control-input.is-invalid ~ .custom-control-indicator {background-color: rgba(220, 53, 69, 0.25); }.was-validated .custom-control-input:invalid ~ .custom-control-description, .custom-control-input.is-invalid ~ .custom-control-description {color: #dc3545; }.was-validated .custom-file-input:invalid ~ .custom-file-control, .custom-file-input.is-invalid ~ .custom-file-control {border-color: #dc3545; }.was-validated .custom-file-input:invalid ~ .custom-file-control::before, .custom-file-input.is-invalid ~ .custom-file-control::before {border-color: inherit; }.was-validated .custom-file-input:invalid:focus, .custom-file-input.is-invalid:focus {box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); }.form-inline {display: flex;flex-flow: row wrap;align-items: center; }.form-inline .form-check {width: 100%; }@media (min-width: 576px) {.form-inline label {display: flex;align-items: center;justify-content: center;margin-bottom: 0; }.form-inline .form-group {display: flex;flex: 0 0 auto;flex-flow: row wrap;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 .input-group {width: auto; }.form-inline .form-control-label {margin-bottom: 0;vertical-align: middle; }.form-inline .form-check {display: flex;align-items: center;justify-content: center;width: auto;margin-top: 0;margin-bottom: 0; }.form-inline .form-check-label {padding-left: 0; }.form-inline .form-check-input {position: relative;margin-top: 0;margin-right: 0.25rem;margin-left: 0; }.form-inline .custom-control {display: flex;align-items: center;justify-content: center;padding-left: 0; }.form-inline .custom-control-indicator {position: static;display: inline-block;margin-right: 0.25rem;vertical-align: text-bottom; }.form-inline .has-feedback .form-control-feedback {top: 0; } }.btn {display: inline-block;font-weight: normal;text-align: center;white-space: nowrap;vertical-align: middle;user-select: none;border: 1px solid transparent;padding: 0.5rem 0.75rem;font-size: 1rem;line-height: 1.25;border-radius: 0.25rem;transition: all 0.15s ease-in-out; }.btn:focus, .btn:hover {text-decoration: none; }.btn:focus, .btn.focus {outline: 0;box-shadow: 0 0 0 3px rgba(0, 164, 220, 0.25); }.btn.disabled, .btn:disabled {opacity: .65; }.btn:active, .btn.active {background-image: none; }a.btn.disabled,fieldset[disabled] a.btn {pointer-events: none; }.btn-primary {color: #fff;background-color: #00a4dc;border-color: #00a4dc; }.btn-primary:hover {color: #fff;background-color: #0087b6;border-color: #007ea9; }.btn-primary:focus, .btn-primary.focus {box-shadow: 0 0 0 3px rgba(0, 164, 220, 0.5); }.btn-primary.disabled, .btn-primary:disabled {background-color: #00a4dc;border-color: #00a4dc; }.btn-primary:active, .btn-primary.active,.show > .btn-primary.dropdown-toggle {background-color: #0087b6;background-image: none;border-color: #007ea9; }.btn-secondary {color: #fff;background-color: #303030;border-color: #303030; }.btn-secondary:hover {color: #fff;background-color: #1d1d1d;border-color: #171717; }.btn-secondary:focus, .btn-secondary.focus {box-shadow: 0 0 0 3px rgba(48, 48, 48, 0.5); }.btn-secondary.disabled, .btn-secondary:disabled {background-color: #303030;border-color: #303030; }.btn-secondary:active, .btn-secondary.active,.show > .btn-secondary.dropdown-toggle {background-color: #1d1d1d;background-image: none;border-color: #171717; }.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 {box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.5); }.btn-success.disabled, .btn-success:disabled {background-color: #28a745;border-color: #28a745; }.btn-success:active, .btn-success.active,.show > .btn-success.dropdown-toggle {background-color: #218838;background-image: none;border-color: #1e7e34; }.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 {box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.5); }.btn-info.disabled, .btn-info:disabled {background-color: #17a2b8;border-color: #17a2b8; }.btn-info:active, .btn-info.active,.show > .btn-info.dropdown-toggle {background-color: #138496;background-image: none;border-color: #117a8b; }.btn-warning {color: #111;background-color: #ffc107;border-color: #ffc107; }.btn-warning:hover {color: #111;background-color: #e0a800;border-color: #d39e00; }.btn-warning:focus, .btn-warning.focus {box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); }.btn-warning.disabled, .btn-warning:disabled {background-color: #ffc107;border-color: #ffc107; }.btn-warning:active, .btn-warning.active,.show > .btn-warning.dropdown-toggle {background-color: #e0a800;background-image: none;border-color: #d39e00; }.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 {box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.5); }.btn-danger.disabled, .btn-danger:disabled {background-color: #dc3545;border-color: #dc3545; }.btn-danger:active, .btn-danger.active,.show > .btn-danger.dropdown-toggle {background-color: #c82333;background-image: none;border-color: #bd2130; }.btn-light {color: #111;background-color: #f8f9fa;border-color: #f8f9fa; }.btn-light:hover {color: #111;background-color: #e2e6ea;border-color: #dae0e5; }.btn-light:focus, .btn-light.focus {box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); }.btn-light.disabled, .btn-light:disabled {background-color: #f8f9fa;border-color: #f8f9fa; }.btn-light:active, .btn-light.active,.show > .btn-light.dropdown-toggle {background-color: #e2e6ea;background-image: none;border-color: #dae0e5; }.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 {box-shadow: 0 0 0 3px rgba(52, 58, 64, 0.5); }.btn-dark.disabled, .btn-dark:disabled {background-color: #343a40;border-color: #343a40; }.btn-dark:active, .btn-dark.active,.show > .btn-dark.dropdown-toggle {background-color: #23272b;background-image: none;border-color: #1d2124; }.btn-outline-primary {color: #00a4dc;background-color: transparent;background-image: none;border-color: #00a4dc; }.btn-outline-primary:hover {color: #fff;background-color: #00a4dc;border-color: #00a4dc; }.btn-outline-primary:focus, .btn-outline-primary.focus {box-shadow: 0 0 0 3px rgba(0, 164, 220, 0.5); }.btn-outline-primary.disabled, .btn-outline-primary:disabled {color: #00a4dc;background-color: transparent; }.btn-outline-primary:active, .btn-outline-primary.active,.show > .btn-outline-primary.dropdown-toggle {color: #fff;background-color: #00a4dc;border-color: #00a4dc; }.btn-outline-secondary {color: #303030;background-color: transparent;background-image: none;border-color: #303030; }.btn-outline-secondary:hover {color: #fff;background-color: #303030;border-color: #303030; }.btn-outline-secondary:focus, .btn-outline-secondary.focus {box-shadow: 0 0 0 3px rgba(48, 48, 48, 0.5); }.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {color: #303030;background-color: transparent; }.btn-outline-secondary:active, .btn-outline-secondary.active,.show > .btn-outline-secondary.dropdown-toggle {color: #fff;background-color: #303030;border-color: #303030; }.btn-outline-success {color: #28a745;background-color: transparent;background-image: none;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 3px rgba(40, 167, 69, 0.5); }.btn-outline-success.disabled, .btn-outline-success:disabled {color: #28a745;background-color: transparent; }.btn-outline-success:active, .btn-outline-success.active,.show > .btn-outline-success.dropdown-toggle {color: #fff;background-color: #28a745;border-color: #28a745; }.btn-outline-info {color: #17a2b8;background-color: transparent;background-image: none;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 3px rgba(23, 162, 184, 0.5); }.btn-outline-info.disabled, .btn-outline-info:disabled {color: #17a2b8;background-color: transparent; }.btn-outline-info:active, .btn-outline-info.active,.show > .btn-outline-info.dropdown-toggle {color: #fff;background-color: #17a2b8;border-color: #17a2b8; }.btn-outline-warning {color: #ffc107;background-color: transparent;background-image: none;border-color: #ffc107; }.btn-outline-warning:hover {color: #fff;background-color: #ffc107;border-color: #ffc107; }.btn-outline-warning:focus, .btn-outline-warning.focus {box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.5); }.btn-outline-warning.disabled, .btn-outline-warning:disabled {color: #ffc107;background-color: transparent; }.btn-outline-warning:active, .btn-outline-warning.active,.show > .btn-outline-warning.dropdown-toggle {color: #fff;background-color: #ffc107;border-color: #ffc107; }.btn-outline-danger {color: #dc3545;background-color: transparent;background-image: none;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 3px rgba(220, 53, 69, 0.5); }.btn-outline-danger.disabled, .btn-outline-danger:disabled {color: #dc3545;background-color: transparent; }.btn-outline-danger:active, .btn-outline-danger.active,.show > .btn-outline-danger.dropdown-toggle {color: #fff;background-color: #dc3545;border-color: #dc3545; }.btn-outline-light {color: #f8f9fa;background-color: transparent;background-image: none;border-color: #f8f9fa; }.btn-outline-light:hover {color: #fff;background-color: #f8f9fa;border-color: #f8f9fa; }.btn-outline-light:focus, .btn-outline-light.focus {box-shadow: 0 0 0 3px rgba(248, 249, 250, 0.5); }.btn-outline-light.disabled, .btn-outline-light:disabled {color: #f8f9fa;background-color: transparent; }.btn-outline-light:active, .btn-outline-light.active,.show > .btn-outline-light.dropdown-toggle {color: #fff;background-color: #f8f9fa;border-color: #f8f9fa; }.btn-outline-dark {color: #343a40;background-color: transparent;background-image: none;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 3px rgba(52, 58, 64, 0.5); }.btn-outline-dark.disabled, .btn-outline-dark:disabled {color: #343a40;background-color: transparent; }.btn-outline-dark:active, .btn-outline-dark.active,.show > .btn-outline-dark.dropdown-toggle {color: #fff;background-color: #343a40;border-color: #343a40; }.btn-link {font-weight: normal;color: #00a4dc;border-radius: 0; }.btn-link, .btn-link:active, .btn-link.active, .btn-link:disabled {background-color: transparent; }.btn-link, .btn-link:focus, .btn-link:active {border-color: transparent;box-shadow: none; }.btn-link:hover {border-color: transparent; }.btn-link:focus, .btn-link:hover {color: #006b90;text-decoration: underline;background-color: transparent; }.btn-link:disabled {color: #303030; }.btn-link:disabled:focus, .btn-link:disabled:hover {text-decoration: none; }.btn-lg, .btn-group-lg > .btn {padding: 0.5rem 1rem;font-size: 1.25rem;line-height: 1.5;border-radius: 0.3rem; }.btn-sm, .btn-group-sm > .btn {padding: 0.25rem 0.5rem;font-size: 0.875rem;line-height: 1.5;border-radius: 0.2rem; }.btn-block {display: block;width: 100%; }.btn-block + .btn-block {margin-top: 0.5rem; }input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block {width: 100%; }.fade {opacity: 0;transition: opacity 0.15s linear; }.fade.show {opacity: 1; }.collapse {display: none; }.collapse.show {display: block; }tr.collapse.show {display: table-row; }tbody.collapse.show {display: table-row-group; }.collapsing {position: relative;height: 0;overflow: hidden;transition: height 0.35s ease; }.dropup,.dropdown {position: relative; }.dropdown-toggle::after {display: inline-block;width: 0;height: 0;margin-left: 0.255em;vertical-align: 0.255em;content: "";border-top: 0.3em solid;border-right: 0.3em solid transparent;border-left: 0.3em solid transparent; }.dropdown-toggle:empty::after {margin-left: 0; }.dropup .dropdown-menu {margin-top: 0;margin-bottom: 0.125rem; }.dropup .dropdown-toggle::after {border-top: 0;border-bottom: 0.3em solid; }.dropdown-menu {position: absolute;top: 100%;left: 0;z-index: 1000;display: none;float: left;min-width: 10rem;padding: 0.5rem 0;margin: 0.125rem 0 0;font-size: 1rem;color: rgba(255, 255, 255, 0.8);text-align: left;list-style: none;background-color: #fff;background-clip: padding-box;border: 1px solid rgba(0, 0, 0, 0.15);border-radius: 0.25rem; }.dropdown-divider {height: 0;margin: 0.5rem 0;overflow: hidden;border-top: 1px solid #e9ecef; }.dropdown-item {display: block;width: 100%;padding: 0.25rem 1.5rem;clear: both;font-weight: normal;color: #212529;text-align: inherit;white-space: nowrap;background: none;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: #00a4dc; }.dropdown-item.disabled, .dropdown-item:disabled {color: #303030;background-color: transparent; }.show > a {outline: 0; }.dropdown-menu.show {display: block; }.dropdown-header {display: block;padding: 0.5rem 1.5rem;margin-bottom: 0;font-size: 0.875rem;color: #303030;white-space: nowrap; }.btn-group,.btn-group-vertical {position: relative;display: inline-flex;vertical-align: middle; }.btn-group > .btn,.btn-group-vertical > .btn {position: relative;flex: 0 1 auto;margin-bottom: 0; }.btn-group > .btn:hover,.btn-group-vertical > .btn:hover {z-index: 2; }.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,.btn-group-vertical > .btn:focus,.btn-group-vertical > .btn:active,.btn-group-vertical > .btn.active {z-index: 2; }.btn-group .btn + .btn,.btn-group .btn + .btn-group,.btn-group .btn-group + .btn,.btn-group .btn-group + .btn-group,.btn-group-vertical .btn + .btn,.btn-group-vertical .btn + .btn-group,.btn-group-vertical .btn-group + .btn,.btn-group-vertical .btn-group + .btn-group {margin-left: -1px; }.btn-toolbar {display: flex;flex-wrap: wrap;justify-content: flex-start; }.btn-toolbar .input-group {width: auto; }.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {border-radius: 0; }.btn-group > .btn:first-child {margin-left: 0; }.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {border-top-right-radius: 0;border-bottom-right-radius: 0; }.btn-group > .btn:last-child:not(:first-child),.btn-group > .dropdown-toggle:not(:first-child) {border-top-left-radius: 0;border-bottom-left-radius: 0; }.btn-group > .btn-group {float: left; }.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {border-radius: 0; }.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {border-top-right-radius: 0;border-bottom-right-radius: 0; }.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {border-top-left-radius: 0;border-bottom-left-radius: 0; }.btn + .dropdown-toggle-split {padding-right: 0.5625rem;padding-left: 0.5625rem; }.btn + .dropdown-toggle-split::after {margin-left: 0; }.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {padding-right: 0.375rem;padding-left: 0.375rem; }.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {padding-right: 0.75rem;padding-left: 0.75rem; }.btn-group-vertical {display: inline-flex;flex-direction: column;align-items: flex-start;justify-content: center; }.btn-group-vertical .btn,.btn-group-vertical .btn-group {width: 100%; }.btn-group-vertical > .btn + .btn,.btn-group-vertical > .btn + .btn-group,.btn-group-vertical > .btn-group + .btn,.btn-group-vertical > .btn-group + .btn-group {margin-top: -1px;margin-left: 0; }.btn-group-vertical > .btn:not(:first-child):not(:last-child) {border-radius: 0; }.btn-group-vertical > .btn:first-child:not(:last-child) {border-bottom-right-radius: 0;border-bottom-left-radius: 0; }.btn-group-vertical > .btn:last-child:not(:first-child) {border-top-left-radius: 0;border-top-right-radius: 0; }.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {border-radius: 0; }.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {border-bottom-right-radius: 0;border-bottom-left-radius: 0; }.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {border-top-left-radius: 0;border-top-right-radius: 0; }[data-toggle="buttons"] > .btn input[type="radio"],[data-toggle="buttons"] > .btn input[type="checkbox"],[data-toggle="buttons"] > .btn-group > .btn input[type="radio"],[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] {position: absolute;clip: rect(0, 0, 0, 0);pointer-events: none; }.input-group {position: relative;display: flex;width: 100%; }.input-group .form-control {position: relative;z-index: 2;flex: 1 1 auto;width: 1%;margin-bottom: 0; }.input-group .form-control:focus, .input-group .form-control:active, .input-group .form-control:hover {z-index: 3; }.input-group-addon,.input-group-btn,.input-group .form-control {display: flex;align-items: center; }.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child) {border-radius: 0; }.input-group-addon,.input-group-btn {white-space: nowrap;vertical-align: middle; }.input-group-addon {padding: 0.5rem 0.75rem;margin-bottom: 0;font-size: 1rem;font-weight: normal;line-height: 1.25;color: #495057;text-align: center;background-color: #e9ecef;border: 1px solid rgba(0, 0, 0, 0.15);border-radius: 0.25rem; }.input-group-addon.form-control-sm,.input-group-sm > .input-group-addon,.input-group-sm > .input-group-btn > .input-group-addon.btn {padding: 0.25rem 0.5rem;font-size: 0.875rem;border-radius: 0.2rem; }.input-group-addon.form-control-lg,.input-group-lg > .input-group-addon,.input-group-lg > .input-group-btn > .input-group-addon.btn {padding: 0.5rem 1rem;font-size: 1.25rem;border-radius: 0.3rem; }.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"] {margin-top: 0; }.input-group .form-control:not(:last-child),.input-group-addon:not(:last-child),.input-group-btn:not(:last-child) > .btn,.input-group-btn:not(:last-child) > .btn-group > .btn,.input-group-btn:not(:last-child) > .dropdown-toggle,.input-group-btn:not(:first-child) > .btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:not(:first-child) > .btn-group:not(:last-child) > .btn {border-top-right-radius: 0;border-bottom-right-radius: 0; }.input-group-addon:not(:last-child) {border-right: 0; }.input-group .form-control:not(:first-child),.input-group-addon:not(:first-child),.input-group-btn:not(:first-child) > .btn,.input-group-btn:not(:first-child) > .btn-group > .btn,.input-group-btn:not(:first-child) > .dropdown-toggle,.input-group-btn:not(:last-child) > .btn:not(:first-child),.input-group-btn:not(:last-child) > .btn-group:not(:first-child) > .btn {border-top-left-radius: 0;border-bottom-left-radius: 0; }.form-control + .input-group-addon:not(:first-child) {border-left: 0; }.input-group-btn {position: relative;font-size: 0;white-space: nowrap; }.input-group-btn > .btn {position: relative; }.input-group-btn > .btn + .btn {margin-left: -1px; }.input-group-btn > .btn:focus, .input-group-btn > .btn:active, .input-group-btn > .btn:hover {z-index: 3; }.input-group-btn:not(:last-child) > .btn,.input-group-btn:not(:last-child) > .btn-group {margin-right: -1px; }.input-group-btn:not(:first-child) > .btn,.input-group-btn:not(:first-child) > .btn-group {z-index: 2;margin-left: -1px; }.input-group-btn:not(:first-child) > .btn:focus, .input-group-btn:not(:first-child) > .btn:active, .input-group-btn:not(:first-child) > .btn:hover,.input-group-btn:not(:first-child) > .btn-group:focus,.input-group-btn:not(:first-child) > .btn-group:active,.input-group-btn:not(:first-child) > .btn-group:hover {z-index: 3; }.custom-control {position: relative;display: inline-flex;min-height: 1.5rem;padding-left: 1.5rem;margin-right: 1rem; }.custom-control-input {position: absolute;z-index: -1;opacity: 0; }.custom-control-input:checked ~ .custom-control-indicator {color: #fff;background-color: #00a4dc; }.custom-control-input:focus ~ .custom-control-indicator {box-shadow: 0 0 0 1px #101010, 0 0 0 3px #00a4dc; }.custom-control-input:active ~ .custom-control-indicator {color: #fff;background-color: #90e3ff; }.custom-control-input:disabled ~ .custom-control-indicator {background-color: #e9ecef; }.custom-control-input:disabled ~ .custom-control-description {color: #303030; }.custom-control-indicator {position: absolute;top: 0.25rem;left: 0;display: block;width: 1rem;height: 1rem;pointer-events: none;user-select: none;background-color: #ddd;background-repeat: no-repeat;background-position: center center;background-size: 50% 50%; }.custom-checkbox .custom-control-indicator {border-radius: 0.25rem; }.custom-checkbox .custom-control-input:checked ~ .custom-control-indicator {background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); }.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-indicator {background-color: #00a4dc;background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); }.custom-radio .custom-control-indicator {border-radius: 50%; }.custom-radio .custom-control-input:checked ~ .custom-control-indicator {background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); }.custom-controls-stacked {display: flex;flex-direction: column; }.custom-controls-stacked .custom-control {margin-bottom: 0.25rem; }.custom-controls-stacked .custom-control + .custom-control {margin-left: 0; }.custom-select {display: inline-block;max-width: 100%;height: calc(2.25rem + 2px);padding: 0.375rem 1.75rem 0.375rem 0.75rem;line-height: 1.25;color: #495057;vertical-align: middle;background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23333' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center;background-size: 8px 10px;border: 1px solid rgba(0, 0, 0, 0.15);border-radius: 0.25rem;appearance: none; }.custom-select:focus {border-color: #5dd6ff;outline: none; }.custom-select:focus::-ms-value {color: #495057;background-color: #fff; }.custom-select:disabled {color: #303030;background-color: #e9ecef; }.custom-select::-ms-expand {opacity: 0; }.custom-select-sm {height: calc(1.8125rem + 2px);padding-top: 0.375rem;padding-bottom: 0.375rem;font-size: 75%; }.custom-file {position: relative;display: inline-block;max-width: 100%;height: 2.5rem;margin-bottom: 0; }.custom-file-input {min-width: 14rem;max-width: 100%;height: 2.5rem;margin: 0;opacity: 0; }.custom-file-control {position: absolute;top: 0;right: 0;left: 0;z-index: 5;height: 2.5rem;padding: 0.5rem 1rem;line-height: 1.5;color: #495057;pointer-events: none;user-select: none;background-color: #fff;border: 1px solid rgba(0, 0, 0, 0.15);border-radius: 0.25rem; }.custom-file-control:lang(en):empty::after {content: "Choose file..."; }.custom-file-control::before {position: absolute;top: -1px;right: -1px;bottom: -1px;z-index: 6;display: block;height: 2.5rem;padding: 0.5rem 1rem;line-height: 1.5;color: #495057;background-color: #e9ecef;border: 1px solid rgba(0, 0, 0, 0.15);border-radius: 0 0.25rem 0.25rem 0; }.custom-file-control:lang(en)::before {content: "Browse"; }.nav {display: flex;flex-wrap: wrap;padding-left: 0;margin-bottom: 0;list-style: none; }.nav-link {display: block;padding: 0.5rem 1rem; }.nav-link:focus, .nav-link:hover {text-decoration: none; }.nav-link.disabled {color: #303030; }.nav-tabs {border-bottom: 1px solid #ddd; }.nav-tabs .nav-item {margin-bottom: -1px; }.nav-tabs .nav-link {border: 1px solid transparent;border-top-left-radius: 0.25rem;border-top-right-radius: 0.25rem; }.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover {border-color: #e9ecef #e9ecef #ddd; }.nav-tabs .nav-link.disabled {color: #303030;background-color: transparent;border-color: transparent; }.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link {color: #495057;background-color: #101010;border-color: #ddd #ddd #101010; }.nav-tabs .dropdown-menu {margin-top: -1px;border-top-left-radius: 0;border-top-right-radius: 0; }.nav-pills .nav-link {border-radius: 0.25rem; }.nav-pills .nav-link.active,.show > .nav-pills .nav-link {color: #fff;background-color: #00a4dc; }.nav-fill .nav-item {flex: 1 1 auto;text-align: center; }.nav-justified .nav-item {flex-basis: 0;flex-grow: 1;text-align: center; }.tab-content > .tab-pane {display: none; }.tab-content > .active {display: block; }.navbar {position: relative;display: flex;flex-wrap: wrap;align-items: center;justify-content: space-between;padding: 0.5rem 1rem; }.navbar > .container,.navbar > .container-fluid {display: flex;flex-wrap: wrap;align-items: center;justify-content: space-between; }.navbar-brand {display: inline-block;padding-top: 0.3125rem;padding-bottom: 0.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: flex;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: 0.5rem;padding-bottom: 0.5rem; }.navbar-collapse {flex-basis: 100%;align-items: center; }.navbar-toggler {padding: 0.25rem 0.75rem;font-size: 1.25rem;line-height: 1;background: transparent;border: 1px solid transparent;border-radius: 0.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: 575px) {.navbar-expand-sm > .container,.navbar-expand-sm > .container-fluid {padding-right: 0;padding-left: 0; } }@media (min-width: 576px) {.navbar-expand-sm {flex-direction: row;flex-wrap: nowrap;justify-content: flex-start; }.navbar-expand-sm .navbar-nav {flex-direction: row; }.navbar-expand-sm .navbar-nav .dropdown-menu {position: absolute; }.navbar-expand-sm .navbar-nav .dropdown-menu-right {right: 0;left: auto; }.navbar-expand-sm .navbar-nav .nav-link {padding-right: .5rem;padding-left: .5rem; }.navbar-expand-sm > .container,.navbar-expand-sm > .container-fluid {flex-wrap: nowrap; }.navbar-expand-sm .navbar-collapse {display: flex !important; }.navbar-expand-sm .navbar-toggler {display: none; } }@media (max-width: 767px) {.navbar-expand-md > .container,.navbar-expand-md > .container-fluid {padding-right: 0;padding-left: 0; } }@media (min-width: 768px) {.navbar-expand-md {flex-direction: row;flex-wrap: nowrap;justify-content: flex-start; }.navbar-expand-md .navbar-nav {flex-direction: row; }.navbar-expand-md .navbar-nav .dropdown-menu {position: absolute; }.navbar-expand-md .navbar-nav .dropdown-menu-right {right: 0;left: auto; }.navbar-expand-md .navbar-nav .nav-link {padding-right: .5rem;padding-left: .5rem; }.navbar-expand-md > .container,.navbar-expand-md > .container-fluid {flex-wrap: nowrap; }.navbar-expand-md .navbar-collapse {display: flex !important; }.navbar-expand-md .navbar-toggler {display: none; } }@media (max-width: 991px) {.navbar-expand-lg > .container,.navbar-expand-lg > .container-fluid {padding-right: 0;padding-left: 0; } }@media (min-width: 992px) {.navbar-expand-lg {flex-direction: row;flex-wrap: nowrap;justify-content: flex-start; }.navbar-expand-lg .navbar-nav {flex-direction: row; }.navbar-expand-lg .navbar-nav .dropdown-menu {position: absolute; }.navbar-expand-lg .navbar-nav .dropdown-menu-right {right: 0;left: auto; }.navbar-expand-lg .navbar-nav .nav-link {padding-right: .5rem;padding-left: .5rem; }.navbar-expand-lg > .container,.navbar-expand-lg > .container-fluid {flex-wrap: nowrap; }.navbar-expand-lg .navbar-collapse {display: flex !important; }.navbar-expand-lg .navbar-toggler {display: none; } }@media (max-width: 1199px) {.navbar-expand-xl > .container,.navbar-expand-xl > .container-fluid {padding-right: 0;padding-left: 0; } }@media (min-width: 1200px) {.navbar-expand-xl {flex-direction: row;flex-wrap: nowrap;justify-content: flex-start; }.navbar-expand-xl .navbar-nav {flex-direction: row; }.navbar-expand-xl .navbar-nav .dropdown-menu {position: absolute; }.navbar-expand-xl .navbar-nav .dropdown-menu-right {right: 0;left: auto; }.navbar-expand-xl .navbar-nav .nav-link {padding-right: .5rem;padding-left: .5rem; }.navbar-expand-xl > .container,.navbar-expand-xl > .container-fluid {flex-wrap: nowrap; }.navbar-expand-xl .navbar-collapse {display: flex !important; }.navbar-expand-xl .navbar-toggler {display: none; } }.navbar-expand {flex-direction: row;flex-wrap: nowrap;justify-content: flex-start; }.navbar-expand > .container,.navbar-expand > .container-fluid {padding-right: 0;padding-left: 0; }.navbar-expand .navbar-nav {flex-direction: row; }.navbar-expand .navbar-nav .dropdown-menu {position: absolute; }.navbar-expand .navbar-nav .dropdown-menu-right {right: 0;left: auto; }.navbar-expand .navbar-nav .nav-link {padding-right: .5rem;padding-left: .5rem; }.navbar-expand > .container,.navbar-expand > .container-fluid {flex-wrap: nowrap; }.navbar-expand .navbar-collapse {display: flex !important; }.navbar-expand .navbar-toggler {display: none; }.navbar-light .navbar-brand {color: rgba(0, 0, 0, 0.9); }.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover {color: rgba(0, 0, 0, 0.9); }.navbar-light .navbar-nav .nav-link {color: rgba(0, 0, 0, 0.5); }.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover {color: rgba(0, 0, 0, 0.7); }.navbar-light .navbar-nav .nav-link.disabled {color: rgba(0, 0, 0, 0.3); }.navbar-light .navbar-nav .show > .nav-link,.navbar-light .navbar-nav .active > .nav-link,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .nav-link.active {color: rgba(0, 0, 0, 0.9); }.navbar-light .navbar-toggler {color: rgba(0, 0, 0, 0.5);border-color: rgba(0, 0, 0, 0.1); }.navbar-light .navbar-toggler-icon {background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }.navbar-light .navbar-text {color: rgba(0, 0, 0, 0.5); }.navbar-dark .navbar-brand {color: white; }.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover {color: white; }.navbar-dark .navbar-nav .nav-link {color: rgba(255, 255, 255, 0.5); }.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover {color: rgba(255, 255, 255, 0.75); }.navbar-dark .navbar-nav .nav-link.disabled {color: rgba(255, 255, 255, 0.25); }.navbar-dark .navbar-nav .show > .nav-link,.navbar-dark .navbar-nav .active > .nav-link,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .nav-link.active {color: white; }.navbar-dark .navbar-toggler {color: rgba(255, 255, 255, 0.5);border-color: rgba(255, 255, 255, 0.1); }.navbar-dark .navbar-toggler-icon {background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); }.navbar-dark .navbar-text {color: rgba(255, 255, 255, 0.5); }.card {position: relative;display: flex;flex-direction: column;min-width: 0;word-wrap: break-word;background-color: #242424;background-clip: border-box;border: 1px solid #242424;border-radius: 0.25rem; }.card-body {flex: 1 1 auto;padding: 1.25rem; }.card-title {margin-bottom: 0.75rem; }.card-subtitle {margin-top: -0.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 > .list-group:first-child .list-group-item:first-child {border-top-left-radius: 0.25rem;border-top-right-radius: 0.25rem; }.card > .list-group:last-child .list-group-item:last-child {border-bottom-right-radius: 0.25rem;border-bottom-left-radius: 0.25rem; }.card-header {padding: 0.75rem 1.25rem;margin-bottom: 0;background-color: #242424;border-bottom: 1px solid #242424; }.card-header:first-child {border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; }.card-footer {padding: 0.75rem 1.25rem;background-color: #242424;border-top: 1px solid #242424; }.card-footer:last-child {border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); }.card-header-tabs {margin-right: -0.625rem;margin-bottom: -0.75rem;margin-left: -0.625rem;border-bottom: 0; }.card-header-pills {margin-right: -0.625rem;margin-left: -0.625rem; }.card-img-overlay {position: absolute;top: 0;right: 0;bottom: 0;left: 0;padding: 1.25rem; }.card-img {width: 100%;border-radius: calc(0.25rem - 1px); }.card-img-top {width: 100%;border-top-left-radius: calc(0.25rem - 1px);border-top-right-radius: calc(0.25rem - 1px); }.card-img-bottom {width: 100%;border-bottom-right-radius: calc(0.25rem - 1px);border-bottom-left-radius: calc(0.25rem - 1px); }@media (min-width: 576px) {.card-deck {display: flex;flex-flow: row wrap;margin-right: -15px;margin-left: -15px; }.card-deck .card {display: flex;flex: 1 0 0%;flex-direction: column;margin-right: 15px;margin-left: 15px; } }@media (min-width: 576px) {.card-group {display: flex;flex-flow: row wrap; }.card-group .card {flex: 1 0 0%; }.card-group .card + .card {margin-left: 0;border-left: 0; }.card-group .card:first-child {border-top-right-radius: 0;border-bottom-right-radius: 0; }.card-group .card:first-child .card-img-top {border-top-right-radius: 0; }.card-group .card:first-child .card-img-bottom {border-bottom-right-radius: 0; }.card-group .card:last-child {border-top-left-radius: 0;border-bottom-left-radius: 0; }.card-group .card:last-child .card-img-top {border-top-left-radius: 0; }.card-group .card:last-child .card-img-bottom {border-bottom-left-radius: 0; }.card-group .card:not(:first-child):not(:last-child) {border-radius: 0; }.card-group .card:not(:first-child):not(:last-child) .card-img-top,.card-group .card:not(:first-child):not(:last-child) .card-img-bottom {border-radius: 0; } }.card-columns .card {margin-bottom: 0.75rem; }@media (min-width: 576px) {.card-columns {column-count: 3;column-gap: 1.25rem; }.card-columns .card {display: inline-block;width: 100%; } }.breadcrumb {padding: 0.75rem 1rem;margin-bottom: 1rem;list-style: none;background-color: #e9ecef;border-radius: 0.25rem; }.breadcrumb::after {display: block;clear: both;content: ""; }.breadcrumb-item {float: left; }.breadcrumb-item + .breadcrumb-item::before {display: inline-block;padding-right: 0.5rem;padding-left: 0.5rem;color: #303030;content: "/"; }.breadcrumb-item + .breadcrumb-item:hover::before {text-decoration: underline; }.breadcrumb-item + .breadcrumb-item:hover::before {text-decoration: none; }.breadcrumb-item.active {color: #303030; }.pagination {display: flex;padding-left: 0;list-style: none;border-radius: 0.25rem; }.page-item:first-child .page-link {margin-left: 0;border-top-left-radius: 0.25rem;border-bottom-left-radius: 0.25rem; }.page-item:last-child .page-link {border-top-right-radius: 0.25rem;border-bottom-right-radius: 0.25rem; }.page-item.active .page-link {z-index: 2;color: #fff;background-color: #00a4dc;border-color: #00a4dc; }.page-item.disabled .page-link {color: #303030;pointer-events: none;background-color: #fff;border-color: #ddd; }.page-link {position: relative;display: block;padding: 0.5rem 0.75rem;margin-left: -1px;line-height: 1.25;color: #00a4dc;background-color: #fff;border: 1px solid #ddd; }.page-link:focus, .page-link:hover {color: #006b90;text-decoration: none;background-color: #e9ecef;border-color: #ddd; }.pagination-lg .page-link {padding: 0.75rem 1.5rem;font-size: 1.25rem;line-height: 1.5; }.pagination-lg .page-item:first-child .page-link {border-top-left-radius: 0.3rem;border-bottom-left-radius: 0.3rem; }.pagination-lg .page-item:last-child .page-link {border-top-right-radius: 0.3rem;border-bottom-right-radius: 0.3rem; }.pagination-sm .page-link {padding: 0.25rem 0.5rem;font-size: 0.875rem;line-height: 1.5; }.pagination-sm .page-item:first-child .page-link {border-top-left-radius: 0.2rem;border-bottom-left-radius: 0.2rem; }.pagination-sm .page-item:last-child .page-link {border-top-right-radius: 0.2rem;border-bottom-right-radius: 0.2rem; }.badge {display: inline-block;padding: 0.25em 0.4em;font-size: 75%;font-weight: bold;line-height: 1;color: #fff;text-align: center;white-space: nowrap;vertical-align: baseline;border-radius: 0.25rem; }.badge:empty {display: none; }.btn .badge {position: relative;top: -1px; }.badge-pill {padding-right: 0.6em;padding-left: 0.6em;border-radius: 10rem; }.badge-primary {color: #fff;background-color: #00a4dc; }.badge-primary[href]:focus, .badge-primary[href]:hover {color: #fff;text-decoration: none;background-color: #007ea9; }.badge-secondary {color: #fff;background-color: #303030; }.badge-secondary[href]:focus, .badge-secondary[href]:hover {color: #fff;text-decoration: none;background-color: #171717; }.badge-success {color: #fff;background-color: #28a745; }.badge-success[href]:focus, .badge-success[href]:hover {color: #fff;text-decoration: none;background-color: #1e7e34; }.badge-info {color: #fff;background-color: #17a2b8; }.badge-info[href]:focus, .badge-info[href]:hover {color: #fff;text-decoration: none;background-color: #117a8b; }.badge-warning {color: #111;background-color: #ffc107; }.badge-warning[href]:focus, .badge-warning[href]:hover {color: #111;text-decoration: none;background-color: #d39e00; }.badge-danger {color: #fff;background-color: #dc3545; }.badge-danger[href]:focus, .badge-danger[href]:hover {color: #fff;text-decoration: none;background-color: #bd2130; }.badge-light {color: #111;background-color: #f8f9fa; }.badge-light[href]:focus, .badge-light[href]:hover {color: #111;text-decoration: none;background-color: #dae0e5; }.badge-dark {color: #fff;background-color: #343a40; }.badge-dark[href]:focus, .badge-dark[href]:hover {color: #fff;text-decoration: none;background-color: #1d2124; }.jumbotron {padding: 2rem 1rem;margin-bottom: 2rem;background-color: #e9ecef;border-radius: 0.3rem; }@media (min-width: 576px) {.jumbotron {padding: 4rem 2rem; } }.jumbotron-fluid {padding-right: 0;padding-left: 0;border-radius: 0; }.alert {padding: 0.75rem 1.25rem;margin-bottom: 1rem;border: 1px solid transparent;border-radius: 0.25rem; }.alert-heading {color: inherit; }.alert-link {font-weight: bold; }.alert-dismissible .close {position: relative;top: -0.75rem;right: -1.25rem;padding: 0.75rem 1.25rem;color: inherit; }.alert-primary {color: #005572;background-color: #ccedf8;border-color: #b8e6f5; }.alert-primary hr {border-top-color: #a2def2; }.alert-primary .alert-link {color: #002f3f; }.alert-secondary {color: #191919;background-color: #d6d6d6;border-color: #c5c5c5; }.alert-secondary hr {border-top-color: #b8b8b8; }.alert-secondary .alert-link {color: black; }.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; }@keyframes progress-bar-stripes {from {background-position: 1rem 0; }to {background-position: 0 0; } }.progress {display: flex;overflow: hidden;font-size: 0.75rem;line-height: 1rem;text-align: center;background-color: #e9ecef;border-radius: 0.25rem; }.progress-bar {height: 1rem;line-height: 1rem;color: #fff;background-color: #00a4dc;transition: width 0.6s ease; }.progress-bar-striped {background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size: 1rem 1rem; }.progress-bar-animated {animation: progress-bar-stripes 1s linear infinite; }.media {display: flex;align-items: flex-start; }.media-body {flex: 1; }.list-group {display: flex;flex-direction: column;padding-left: 0;margin-bottom: 0; }.list-group-item-action {width: 100%;color: #495057;text-align: inherit; }.list-group-item-action:focus, .list-group-item-action:hover {color: #495057;text-decoration: none;background-color: #f8f9fa; }.list-group-item-action:active {color: rgba(255, 255, 255, 0.8);background-color: #e9ecef; }.list-group-item {position: relative;display: block;padding: 0.75rem 1.25rem;margin-bottom: -1px;background-color: #fff;border: 1px solid rgba(0, 0, 0, 0.125); }.list-group-item:first-child {border-top-left-radius: 0.25rem;border-top-right-radius: 0.25rem; }.list-group-item:last-child {margin-bottom: 0;border-bottom-right-radius: 0.25rem;border-bottom-left-radius: 0.25rem; }.list-group-item:focus, .list-group-item:hover {text-decoration: none; }.list-group-item.disabled, .list-group-item:disabled {color: #303030;background-color: #fff; }.list-group-item.active {z-index: 2;color: #fff;background-color: #00a4dc;border-color: #00a4dc; }.list-group-flush .list-group-item {border-right: 0;border-left: 0;border-radius: 0; }.list-group-flush:first-child .list-group-item:first-child {border-top: 0; }.list-group-flush:last-child .list-group-item:last-child {border-bottom: 0; }.list-group-item-primary {color: #005572;background-color: #b8e6f5; }a.list-group-item-primary,button.list-group-item-primary {color: #005572; }a.list-group-item-primary:focus, a.list-group-item-primary:hover,button.list-group-item-primary:focus,button.list-group-item-primary:hover {color: #005572;background-color: #a2def2; }a.list-group-item-primary.active,button.list-group-item-primary.active {color: #fff;background-color: #005572;border-color: #005572; }.list-group-item-secondary {color: #191919;background-color: #c5c5c5; }a.list-group-item-secondary,button.list-group-item-secondary {color: #191919; }a.list-group-item-secondary:focus, a.list-group-item-secondary:hover,button.list-group-item-secondary:focus,button.list-group-item-secondary:hover {color: #191919;background-color: #b8b8b8; }a.list-group-item-secondary.active,button.list-group-item-secondary.active {color: #fff;background-color: #191919;border-color: #191919; }.list-group-item-success {color: #155724;background-color: #c3e6cb; }a.list-group-item-success,button.list-group-item-success {color: #155724; }a.list-group-item-success:focus, a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover {color: #155724;background-color: #b1dfbb; }a.list-group-item-success.active,button.list-group-item-success.active {color: #fff;background-color: #155724;border-color: #155724; }.list-group-item-info {color: #0c5460;background-color: #bee5eb; }a.list-group-item-info,button.list-group-item-info {color: #0c5460; }a.list-group-item-info:focus, a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover {color: #0c5460;background-color: #abdde5; }a.list-group-item-info.active,button.list-group-item-info.active {color: #fff;background-color: #0c5460;border-color: #0c5460; }.list-group-item-warning {color: #856404;background-color: #ffeeba; }a.list-group-item-warning,button.list-group-item-warning {color: #856404; }a.list-group-item-warning:focus, a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover {color: #856404;background-color: #ffe8a1; }a.list-group-item-warning.active,button.list-group-item-warning.active {color: #fff;background-color: #856404;border-color: #856404; }.list-group-item-danger {color: #721c24;background-color: #f5c6cb; }a.list-group-item-danger,button.list-group-item-danger {color: #721c24; }a.list-group-item-danger:focus, a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover {color: #721c24;background-color: #f1b0b7; }a.list-group-item-danger.active,button.list-group-item-danger.active {color: #fff;background-color: #721c24;border-color: #721c24; }.list-group-item-light {color: #818182;background-color: #fdfdfe; }a.list-group-item-light,button.list-group-item-light {color: #818182; }a.list-group-item-light:focus, a.list-group-item-light:hover,button.list-group-item-light:focus,button.list-group-item-light:hover {color: #818182;background-color: #ececf6; }a.list-group-item-light.active,button.list-group-item-light.active {color: #fff;background-color: #818182;border-color: #818182; }.list-group-item-dark {color: #1b1e21;background-color: #c6c8ca; }a.list-group-item-dark,button.list-group-item-dark {color: #1b1e21; }a.list-group-item-dark:focus, a.list-group-item-dark:hover,button.list-group-item-dark:focus,button.list-group-item-dark:hover {color: #1b1e21;background-color: #b9bbbe; }a.list-group-item-dark.active,button.list-group-item-dark.active {color: #fff;background-color: #1b1e21;border-color: #1b1e21; }.close {float: right;font-size: 1.5rem;font-weight: bold;line-height: 1;color: rgba(255, 255, 255, 0.8);text-shadow: 0 1px 0 #000;opacity: .5; }.close:focus, .close:hover {color: rgba(255, 255, 255, 0.8);text-decoration: none;opacity: .75; }button.close {padding: 0;background: transparent;border: 0;-webkit-appearance: none; }.modal-open {overflow: hidden; }.modal {position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 1050;display: none;overflow: hidden;outline: 0; }.modal.fade .modal-dialog {transition: transform 0.3s ease-out;transform: translate(0, -25%); }.modal.show .modal-dialog {transform: translate(0, 0); }.modal-open .modal {overflow-x: hidden;overflow-y: auto; }.modal-dialog {position: relative;width: auto;margin: 10px; }.modal-content {position: relative;display: flex;flex-direction: column;background-color: #242424;background-clip: padding-box;border: 1px solid rgba(0, 0, 0, 0.2);border-radius: 0.3rem;outline: 0; }.modal-backdrop {position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 1040;background-color: #000; }.modal-backdrop.fade {opacity: 0; }.modal-backdrop.show {opacity: 0.5; }.modal-header {display: flex;align-items: center;justify-content: space-between;padding: 15px;border-bottom: 1px solid #242424; }.modal-title {margin-bottom: 0;line-height: 1.5; }.modal-body {position: relative;flex: 1 1 auto;padding: 15px; }.modal-footer {display: flex;align-items: center;justify-content: flex-end;padding: 15px;border-top: 1px solid #242424; }.modal-footer > :not(:first-child) {margin-left: 0.25rem; }.modal-footer > :not(:last-child) {margin-right: 0.25rem; }.modal-scrollbar-measure {position: absolute;top: -9999px;width: 50px;height: 50px;overflow: scroll; }@media (min-width: 576px) {.modal-dialog {max-width: 500px;margin: 30px auto; }.modal-sm {max-width: 300px; } }@media (min-width: 992px) {.modal-lg {max-width: 800px; } }.tooltip {position: absolute;z-index: 1070;display: block;margin: 0;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-style: normal;font-weight: normal;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: 0.875rem;word-wrap: break-word;opacity: 0; }.tooltip.show {opacity: 0.9; }.tooltip .arrow {position: absolute;display: block;width: 5px;height: 5px; }.tooltip.bs-tooltip-top, .tooltip.bs-tooltip-auto[x-placement^="top"] {padding: 5px 0; }.tooltip.bs-tooltip-top .arrow, .tooltip.bs-tooltip-auto[x-placement^="top"] .arrow {bottom: 0; }.tooltip.bs-tooltip-top .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="top"] .arrow::before {margin-left: -3px;content: "";border-width: 5px 5px 0;border-top-color: #000; }.tooltip.bs-tooltip-right, .tooltip.bs-tooltip-auto[x-placement^="right"] {padding: 0 5px; }.tooltip.bs-tooltip-right .arrow, .tooltip.bs-tooltip-auto[x-placement^="right"] .arrow {left: 0; }.tooltip.bs-tooltip-right .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="right"] .arrow::before {margin-top: -3px;content: "";border-width: 5px 5px 5px 0;border-right-color: #000; }.tooltip.bs-tooltip-bottom, .tooltip.bs-tooltip-auto[x-placement^="bottom"] {padding: 5px 0; }.tooltip.bs-tooltip-bottom .arrow, .tooltip.bs-tooltip-auto[x-placement^="bottom"] .arrow {top: 0; }.tooltip.bs-tooltip-bottom .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="bottom"] .arrow::before {margin-left: -3px;content: "";border-width: 0 5px 5px;border-bottom-color: #000; }.tooltip.bs-tooltip-left, .tooltip.bs-tooltip-auto[x-placement^="left"] {padding: 0 5px; }.tooltip.bs-tooltip-left .arrow, .tooltip.bs-tooltip-auto[x-placement^="left"] .arrow {right: 0; }.tooltip.bs-tooltip-left .arrow::before, .tooltip.bs-tooltip-auto[x-placement^="left"] .arrow::before {right: 0;margin-top: -3px;content: "";border-width: 5px 0 5px 5px;border-left-color: #000; }.tooltip .arrow::before {position: absolute;border-color: transparent;border-style: solid; }.tooltip-inner {max-width: 200px;padding: 3px 8px;color: #fff;text-align: center;background-color: #000;border-radius: 0.25rem; }.popover {position: absolute;top: 0;left: 0;z-index: 1060;display: block;max-width: 276px;padding: 1px;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-style: normal;font-weight: normal;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: 0.875rem;word-wrap: break-word;background-color: #fff;background-clip: padding-box;border: 1px solid rgba(0, 0, 0, 0.2);border-radius: 0.3rem; }.popover .arrow {position: absolute;display: block;width: 10px;height: 5px; }.popover .arrow::before,.popover .arrow::after {position: absolute;display: block;border-color: transparent;border-style: solid; }.popover .arrow::before {content: "";border-width: 11px; }.popover .arrow::after {content: "";border-width: 11px; }.popover.bs-popover-top, .popover.bs-popover-auto[x-placement^="top"] {margin-bottom: 10px; }.popover.bs-popover-top .arrow, .popover.bs-popover-auto[x-placement^="top"] .arrow {bottom: 0; }.popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^="top"] .arrow::before,.popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^="top"] .arrow::after {border-bottom-width: 0; }.popover.bs-popover-top .arrow::before, .popover.bs-popover-auto[x-placement^="top"] .arrow::before {bottom: -11px;margin-left: -6px;border-top-color: rgba(0, 0, 0, 0.25); }.popover.bs-popover-top .arrow::after, .popover.bs-popover-auto[x-placement^="top"] .arrow::after {bottom: -10px;margin-left: -6px;border-top-color: #fff; }.popover.bs-popover-right, .popover.bs-popover-auto[x-placement^="right"] {margin-left: 10px; }.popover.bs-popover-right .arrow, .popover.bs-popover-auto[x-placement^="right"] .arrow {left: 0; }.popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^="right"] .arrow::before,.popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^="right"] .arrow::after {margin-top: -8px;border-left-width: 0; }.popover.bs-popover-right .arrow::before, .popover.bs-popover-auto[x-placement^="right"] .arrow::before {left: -11px;border-right-color: rgba(0, 0, 0, 0.25); }.popover.bs-popover-right .arrow::after, .popover.bs-popover-auto[x-placement^="right"] .arrow::after {left: -10px;border-right-color: #fff; }.popover.bs-popover-bottom, .popover.bs-popover-auto[x-placement^="bottom"] {margin-top: 10px; }.popover.bs-popover-bottom .arrow, .popover.bs-popover-auto[x-placement^="bottom"] .arrow {top: 0; }.popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::before,.popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::after {margin-left: -7px;border-top-width: 0; }.popover.bs-popover-bottom .arrow::before, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::before {top: -11px;border-bottom-color: rgba(0, 0, 0, 0.25); }.popover.bs-popover-bottom .arrow::after, .popover.bs-popover-auto[x-placement^="bottom"] .arrow::after {top: -10px;border-bottom-color: #fff; }.popover.bs-popover-bottom .popover-header::before, .popover.bs-popover-auto[x-placement^="bottom"] .popover-header::before {position: absolute;top: 0;left: 50%;display: block;width: 20px;margin-left: -10px;content: "";border-bottom: 1px solid #f7f7f7; }.popover.bs-popover-left, .popover.bs-popover-auto[x-placement^="left"] {margin-right: 10px; }.popover.bs-popover-left .arrow, .popover.bs-popover-auto[x-placement^="left"] .arrow {right: 0; }.popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^="left"] .arrow::before,.popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^="left"] .arrow::after {margin-top: -8px;border-right-width: 0; }.popover.bs-popover-left .arrow::before, .popover.bs-popover-auto[x-placement^="left"] .arrow::before {right: -11px;border-left-color: rgba(0, 0, 0, 0.25); }.popover.bs-popover-left .arrow::after, .popover.bs-popover-auto[x-placement^="left"] .arrow::after {right: -10px;border-left-color: #fff; }.popover-header {padding: 8px 14px;margin-bottom: 0;font-size: 1rem;color: inherit;background-color: #f7f7f7;border-bottom: 1px solid #ebebeb;border-top-left-radius: calc(0.3rem - 1px);border-top-right-radius: calc(0.3rem - 1px); }.popover-header:empty {display: none; }.popover-body {padding: 9px 14px;color: rgba(255, 255, 255, 0.8); }.carousel {position: relative; }.carousel-inner {position: relative;width: 100%;overflow: hidden; }.carousel-item {position: relative;display: none;align-items: center;width: 100%;transition: transform 0.6s ease;backface-visibility: hidden;perspective: 1000px; }.carousel-item.active,.carousel-item-next,.carousel-item-prev {display: block; }.carousel-item-next,.carousel-item-prev {position: absolute;top: 0; }.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right {transform: translateX(0); }@supports (transform-style: preserve-3d) {.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right {transform: translate3d(0, 0, 0); } }.carousel-item-next,.active.carousel-item-right {transform: translateX(100%); }@supports (transform-style: preserve-3d) {.carousel-item-next,.active.carousel-item-right {transform: translate3d(100%, 0, 0); } }.carousel-item-prev,.active.carousel-item-left {transform: translateX(-100%); }@supports (transform-style: preserve-3d) {.carousel-item-prev,.active.carousel-item-left {transform: translate3d(-100%, 0, 0); } }.carousel-control-prev,.carousel-control-next {position: absolute;top: 0;bottom: 0;display: flex;align-items: center;justify-content: center;width: 15%;color: #fff;text-align: center;opacity: 0.5; }.carousel-control-prev:focus, .carousel-control-prev:hover,.carousel-control-next:focus,.carousel-control-next:hover {color: #fff;text-decoration: none;outline: 0;opacity: .9; }.carousel-control-prev {left: 0; }.carousel-control-next {right: 0; }.carousel-control-prev-icon,.carousel-control-next-icon {display: inline-block;width: 20px;height: 20px;background: transparent no-repeat center center;background-size: 100% 100%; }.carousel-control-prev-icon {background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M4 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); }.carousel-control-next-icon {background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M1.5 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); }.carousel-indicators {position: absolute;right: 0;bottom: 10px;left: 0;z-index: 15;display: flex;justify-content: center;padding-left: 0;margin-right: 15%;margin-left: 15%;list-style: none; }.carousel-indicators li {position: relative;flex: 0 1 auto;width: 30px;height: 3px;margin-right: 3px;margin-left: 3px;text-indent: -999px;background-color: rgba(255, 255, 255, 0.5); }.carousel-indicators li::before {position: absolute;top: -10px;left: 0;display: inline-block;width: 100%;height: 10px;content: ""; }.carousel-indicators li::after {position: absolute;bottom: -10px;left: 0;display: inline-block;width: 100%;height: 10px;content: ""; }.carousel-indicators .active {background-color: #fff; }.carousel-caption {position: absolute;right: 15%;bottom: 20px;left: 15%;z-index: 10;padding-top: 20px;padding-bottom: 20px;color: #fff;text-align: center; }.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: #00a4dc !important; }a.bg-primary:focus, a.bg-primary:hover {background-color: #007ea9 !important; }.bg-secondary {background-color: #303030 !important; }a.bg-secondary:focus, a.bg-secondary:hover {background-color: #171717 !important; }.bg-success {background-color: #28a745 !important; }a.bg-success:focus, a.bg-success:hover {background-color: #1e7e34 !important; }.bg-info {background-color: #17a2b8 !important; }a.bg-info:focus, a.bg-info:hover {background-color: #117a8b !important; }.bg-warning {background-color: #ffc107 !important; }a.bg-warning:focus, a.bg-warning:hover {background-color: #d39e00 !important; }.bg-danger {background-color: #dc3545 !important; }a.bg-danger:focus, a.bg-danger:hover {background-color: #bd2130 !important; }.bg-light {background-color: #f8f9fa !important; }a.bg-light:focus, a.bg-light:hover {background-color: #dae0e5 !important; }.bg-dark {background-color: #343a40 !important; }a.bg-dark:focus, a.bg-dark:hover {background-color: #1d2124 !important; }.bg-white {background-color: #fff !important; }.bg-transparent {background-color: transparent !important; }.border {border: 1px solid #e9ecef !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: #00a4dc !important; }.border-secondary {border-color: #303030 !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 {border-radius: 0.25rem !important; }.rounded-top {border-top-left-radius: 0.25rem !important;border-top-right-radius: 0.25rem !important; }.rounded-right {border-top-right-radius: 0.25rem !important;border-bottom-right-radius: 0.25rem !important; }.rounded-bottom {border-bottom-right-radius: 0.25rem !important;border-bottom-left-radius: 0.25rem !important; }.rounded-left {border-top-left-radius: 0.25rem !important;border-bottom-left-radius: 0.25rem !important; }.rounded-circle {border-radius: 50%; }.rounded-0 {border-radius: 0; }.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-cell {display: table-cell !important; }.d-flex {display: flex !important; }.d-inline-flex {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-cell {display: table-cell !important; }.d-sm-flex {display: flex !important; }.d-sm-inline-flex {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-cell {display: table-cell !important; }.d-md-flex {display: flex !important; }.d-md-inline-flex {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-cell {display: table-cell !important; }.d-lg-flex {display: flex !important; }.d-lg-inline-flex {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-cell {display: table-cell !important; }.d-xl-flex {display: flex !important; }.d-xl-inline-flex {display: inline-flex !important; } }.d-print-block {display: none !important; }@media print {.d-print-block {display: block !important; } }.d-print-inline {display: none !important; }@media print {.d-print-inline {display: inline !important; } }.d-print-inline-block {display: none !important; }@media print {.d-print-inline-block {display: inline-block !important; } }@media print {.d-print-none {display: none !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 iframe,.embed-responsive embed,.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.85714%; }.embed-responsive-16by9::before {padding-top: 56.25%; }.embed-responsive-4by3::before {padding-top: 75%; }.embed-responsive-1by1::before {padding-top: 100%; }.flex-row {flex-direction: row !important; }.flex-column {flex-direction: column !important; }.flex-row-reverse {flex-direction: row-reverse !important; }.flex-column-reverse {flex-direction: column-reverse !important; }.flex-wrap {flex-wrap: wrap !important; }.flex-nowrap {flex-wrap: nowrap !important; }.flex-wrap-reverse {flex-wrap: wrap-reverse !important; }.justify-content-start {justify-content: flex-start !important; }.justify-content-end {justify-content: flex-end !important; }.justify-content-center {justify-content: center !important; }.justify-content-between {justify-content: space-between !important; }.justify-content-around {justify-content: space-around !important; }.align-items-start {align-items: flex-start !important; }.align-items-end {align-items: flex-end !important; }.align-items-center {align-items: center !important; }.align-items-baseline {align-items: baseline !important; }.align-items-stretch {align-items: stretch !important; }.align-content-start {align-content: flex-start !important; }.align-content-end {align-content: flex-end !important; }.align-content-center {align-content: center !important; }.align-content-between {align-content: space-between !important; }.align-content-around {align-content: space-around !important; }.align-content-stretch {align-content: stretch !important; }.align-self-auto {align-self: auto !important; }.align-self-start {align-self: flex-start !important; }.align-self-end {align-self: flex-end !important; }.align-self-center {align-self: center !important; }.align-self-baseline {align-self: baseline !important; }.align-self-stretch {align-self: stretch !important; }@media (min-width: 576px) {.flex-sm-row {flex-direction: row !important; }.flex-sm-column {flex-direction: column !important; }.flex-sm-row-reverse {flex-direction: row-reverse !important; }.flex-sm-column-reverse {flex-direction: column-reverse !important; }.flex-sm-wrap {flex-wrap: wrap !important; }.flex-sm-nowrap {flex-wrap: nowrap !important; }.flex-sm-wrap-reverse {flex-wrap: wrap-reverse !important; }.justify-content-sm-start {justify-content: flex-start !important; }.justify-content-sm-end {justify-content: flex-end !important; }.justify-content-sm-center {justify-content: center !important; }.justify-content-sm-between {justify-content: space-between !important; }.justify-content-sm-around {justify-content: space-around !important; }.align-items-sm-start {align-items: flex-start !important; }.align-items-sm-end {align-items: flex-end !important; }.align-items-sm-center {align-items: center !important; }.align-items-sm-baseline {align-items: baseline !important; }.align-items-sm-stretch {align-items: stretch !important; }.align-content-sm-start {align-content: flex-start !important; }.align-content-sm-end {align-content: flex-end !important; }.align-content-sm-center {align-content: center !important; }.align-content-sm-between {align-content: space-between !important; }.align-content-sm-around {align-content: space-around !important; }.align-content-sm-stretch {align-content: stretch !important; }.align-self-sm-auto {align-self: auto !important; }.align-self-sm-start {align-self: flex-start !important; }.align-self-sm-end {align-self: flex-end !important; }.align-self-sm-center {align-self: center !important; }.align-self-sm-baseline {align-self: baseline !important; }.align-self-sm-stretch {align-self: stretch !important; } }@media (min-width: 768px) {.flex-md-row {flex-direction: row !important; }.flex-md-column {flex-direction: column !important; }.flex-md-row-reverse {flex-direction: row-reverse !important; }.flex-md-column-reverse {flex-direction: column-reverse !important; }.flex-md-wrap {flex-wrap: wrap !important; }.flex-md-nowrap {flex-wrap: nowrap !important; }.flex-md-wrap-reverse {flex-wrap: wrap-reverse !important; }.justify-content-md-start {justify-content: flex-start !important; }.justify-content-md-end {justify-content: flex-end !important; }.justify-content-md-center {justify-content: center !important; }.justify-content-md-between {justify-content: space-between !important; }.justify-content-md-around {justify-content: space-around !important; }.align-items-md-start {align-items: flex-start !important; }.align-items-md-end {align-items: flex-end !important; }.align-items-md-center {align-items: center !important; }.align-items-md-baseline {align-items: baseline !important; }.align-items-md-stretch {align-items: stretch !important; }.align-content-md-start {align-content: flex-start !important; }.align-content-md-end {align-content: flex-end !important; }.align-content-md-center {align-content: center !important; }.align-content-md-between {align-content: space-between !important; }.align-content-md-around {align-content: space-around !important; }.align-content-md-stretch {align-content: stretch !important; }.align-self-md-auto {align-self: auto !important; }.align-self-md-start {align-self: flex-start !important; }.align-self-md-end {align-self: flex-end !important; }.align-self-md-center {align-self: center !important; }.align-self-md-baseline {align-self: baseline !important; }.align-self-md-stretch {align-self: stretch !important; } }@media (min-width: 992px) {.flex-lg-row {flex-direction: row !important; }.flex-lg-column {flex-direction: column !important; }.flex-lg-row-reverse {flex-direction: row-reverse !important; }.flex-lg-column-reverse {flex-direction: column-reverse !important; }.flex-lg-wrap {flex-wrap: wrap !important; }.flex-lg-nowrap {flex-wrap: nowrap !important; }.flex-lg-wrap-reverse {flex-wrap: wrap-reverse !important; }.justify-content-lg-start {justify-content: flex-start !important; }.justify-content-lg-end {justify-content: flex-end !important; }.justify-content-lg-center {justify-content: center !important; }.justify-content-lg-between {justify-content: space-between !important; }.justify-content-lg-around {justify-content: space-around !important; }.align-items-lg-start {align-items: flex-start !important; }.align-items-lg-end {align-items: flex-end !important; }.align-items-lg-center {align-items: center !important; }.align-items-lg-baseline {align-items: baseline !important; }.align-items-lg-stretch {align-items: stretch !important; }.align-content-lg-start {align-content: flex-start !important; }.align-content-lg-end {align-content: flex-end !important; }.align-content-lg-center {align-content: center !important; }.align-content-lg-between {align-content: space-between !important; }.align-content-lg-around {align-content: space-around !important; }.align-content-lg-stretch {align-content: stretch !important; }.align-self-lg-auto {align-self: auto !important; }.align-self-lg-start {align-self: flex-start !important; }.align-self-lg-end {align-self: flex-end !important; }.align-self-lg-center {align-self: center !important; }.align-self-lg-baseline {align-self: baseline !important; }.align-self-lg-stretch {align-self: stretch !important; } }@media (min-width: 1200px) {.flex-xl-row {flex-direction: row !important; }.flex-xl-column {flex-direction: column !important; }.flex-xl-row-reverse {flex-direction: row-reverse !important; }.flex-xl-column-reverse {flex-direction: column-reverse !important; }.flex-xl-wrap {flex-wrap: wrap !important; }.flex-xl-nowrap {flex-wrap: nowrap !important; }.flex-xl-wrap-reverse {flex-wrap: wrap-reverse !important; }.justify-content-xl-start {justify-content: flex-start !important; }.justify-content-xl-end {justify-content: flex-end !important; }.justify-content-xl-center {justify-content: center !important; }.justify-content-xl-between {justify-content: space-between !important; }.justify-content-xl-around {justify-content: space-around !important; }.align-items-xl-start {align-items: flex-start !important; }.align-items-xl-end {align-items: flex-end !important; }.align-items-xl-center {align-items: center !important; }.align-items-xl-baseline {align-items: baseline !important; }.align-items-xl-stretch {align-items: stretch !important; }.align-content-xl-start {align-content: flex-start !important; }.align-content-xl-end {align-content: flex-end !important; }.align-content-xl-center {align-content: center !important; }.align-content-xl-between {align-content: space-between !important; }.align-content-xl-around {align-content: space-around !important; }.align-content-xl-stretch {align-content: stretch !important; }.align-self-xl-auto {align-self: auto !important; }.align-self-xl-start {align-self: flex-start !important; }.align-self-xl-end {align-self: flex-end !important; }.align-self-xl-center {align-self: center !important; }.align-self-xl-baseline {align-self: baseline !important; }.align-self-xl-stretch {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; } }.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: sticky) {.sticky-top {position: sticky;top: 0;z-index: 1020; } }.sr-only {position: absolute;width: 1px;height: 1px;padding: 0;overflow: hidden;clip: rect(0, 0, 0, 0);white-space: nowrap;clip-path: inset(50%);border: 0; }.sr-only-focusable:active, .sr-only-focusable:focus {position: static;width: auto;height: auto;overflow: visible;clip: auto;white-space: normal;clip-path: none; }.w-25 {width: 25% !important; }.w-50 {width: 50% !important; }.w-75 {width: 75% !important; }.w-100 {width: 100% !important; }.h-25 {height: 25% !important; }.h-50 {height: 50% !important; }.h-75 {height: 75% !important; }.h-100 {height: 100% !important; }.mw-100 {max-width: 100% !important; }.mh-100 {max-height: 100% !important; }.m-0 {margin: 0 !important; }.mt-0 {margin-top: 0 !important; }.mr-0 {margin-right: 0 !important; }.mb-0 {margin-bottom: 0 !important; }.ml-0 {margin-left: 0 !important; }.mx-0 {margin-right: 0 !important;margin-left: 0 !important; }.my-0 {margin-top: 0 !important;margin-bottom: 0 !important; }.m-1 {margin: 0.25rem !important; }.mt-1 {margin-top: 0.25rem !important; }.mr-1 {margin-right: 0.25rem !important; }.mb-1 {margin-bottom: 0.25rem !important; }.ml-1 {margin-left: 0.25rem !important; }.mx-1 {margin-right: 0.25rem !important;margin-left: 0.25rem !important; }.my-1 {margin-top: 0.25rem !important;margin-bottom: 0.25rem !important; }.m-2 {margin: 0.5rem !important; }.mt-2 {margin-top: 0.5rem !important; }.mr-2 {margin-right: 0.5rem !important; }.mb-2 {margin-bottom: 0.5rem !important; }.ml-2 {margin-left: 0.5rem !important; }.mx-2 {margin-right: 0.5rem !important;margin-left: 0.5rem !important; }.my-2 {margin-top: 0.5rem !important;margin-bottom: 0.5rem !important; }.m-3 {margin: 1rem !important; }.mt-3 {margin-top: 1rem !important; }.mr-3 {margin-right: 1rem !important; }.mb-3 {margin-bottom: 1rem !important; }.ml-3 {margin-left: 1rem !important; }.mx-3 {margin-right: 1rem !important;margin-left: 1rem !important; }.my-3 {margin-top: 1rem !important;margin-bottom: 1rem !important; }.m-4 {margin: 1.5rem !important; }.mt-4 {margin-top: 1.5rem !important; }.mr-4 {margin-right: 1.5rem !important; }.mb-4 {margin-bottom: 1.5rem !important; }.ml-4 {margin-left: 1.5rem !important; }.mx-4 {margin-right: 1.5rem !important;margin-left: 1.5rem !important; }.my-4 {margin-top: 1.5rem !important;margin-bottom: 1.5rem !important; }.m-5 {margin: 3rem !important; }.mt-5 {margin-top: 3rem !important; }.mr-5 {margin-right: 3rem !important; }.mb-5 {margin-bottom: 3rem !important; }.ml-5 {margin-left: 3rem !important; }.mx-5 {margin-right: 3rem !important;margin-left: 3rem !important; }.my-5 {margin-top: 3rem !important;margin-bottom: 3rem !important; }.p-0 {padding: 0 !important; }.pt-0 {padding-top: 0 !important; }.pr-0 {padding-right: 0 !important; }.pb-0 {padding-bottom: 0 !important; }.pl-0 {padding-left: 0 !important; }.px-0 {padding-right: 0 !important;padding-left: 0 !important; }.py-0 {padding-top: 0 !important;padding-bottom: 0 !important; }.p-1 {padding: 0.25rem !important; }.pt-1 {padding-top: 0.25rem !important; }.pr-1 {padding-right: 0.25rem !important; }.pb-1 {padding-bottom: 0.25rem !important; }.pl-1 {padding-left: 0.25rem !important; }.px-1 {padding-right: 0.25rem !important;padding-left: 0.25rem !important; }.py-1 {padding-top: 0.25rem !important;padding-bottom: 0.25rem !important; }.p-2 {padding: 0.5rem !important; }.pt-2 {padding-top: 0.5rem !important; }.pr-2 {padding-right: 0.5rem !important; }.pb-2 {padding-bottom: 0.5rem !important; }.pl-2 {padding-left: 0.5rem !important; }.px-2 {padding-right: 0.5rem !important;padding-left: 0.5rem !important; }.py-2 {padding-top: 0.5rem !important;padding-bottom: 0.5rem !important; }.p-3 {padding: 1rem !important; }.pt-3 {padding-top: 1rem !important; }.pr-3 {padding-right: 1rem !important; }.pb-3 {padding-bottom: 1rem !important; }.pl-3 {padding-left: 1rem !important; }.px-3 {padding-right: 1rem !important;padding-left: 1rem !important; }.py-3 {padding-top: 1rem !important;padding-bottom: 1rem !important; }.p-4 {padding: 1.5rem !important; }.pt-4 {padding-top: 1.5rem !important; }.pr-4 {padding-right: 1.5rem !important; }.pb-4 {padding-bottom: 1.5rem !important; }.pl-4 {padding-left: 1.5rem !important; }.px-4 {padding-right: 1.5rem !important;padding-left: 1.5rem !important; }.py-4 {padding-top: 1.5rem !important;padding-bottom: 1.5rem !important; }.p-5 {padding: 3rem !important; }.pt-5 {padding-top: 3rem !important; }.pr-5 {padding-right: 3rem !important; }.pb-5 {padding-bottom: 3rem !important; }.pl-5 {padding-left: 3rem !important; }.px-5 {padding-right: 3rem !important;padding-left: 3rem !important; }.py-5 {padding-top: 3rem !important;padding-bottom: 3rem !important; }.m-auto {margin: auto !important; }.mt-auto {margin-top: auto !important; }.mr-auto {margin-right: auto !important; }.mb-auto {margin-bottom: auto !important; }.ml-auto {margin-left: auto !important; }.mx-auto {margin-right: auto !important;margin-left: auto !important; }.my-auto {margin-top: auto !important;margin-bottom: auto !important; }@media (min-width: 576px) {.m-sm-0 {margin: 0 !important; }.mt-sm-0 {margin-top: 0 !important; }.mr-sm-0 {margin-right: 0 !important; }.mb-sm-0 {margin-bottom: 0 !important; }.ml-sm-0 {margin-left: 0 !important; }.mx-sm-0 {margin-right: 0 !important;margin-left: 0 !important; }.my-sm-0 {margin-top: 0 !important;margin-bottom: 0 !important; }.m-sm-1 {margin: 0.25rem !important; }.mt-sm-1 {margin-top: 0.25rem !important; }.mr-sm-1 {margin-right: 0.25rem !important; }.mb-sm-1 {margin-bottom: 0.25rem !important; }.ml-sm-1 {margin-left: 0.25rem !important; }.mx-sm-1 {margin-right: 0.25rem !important;margin-left: 0.25rem !important; }.my-sm-1 {margin-top: 0.25rem !important;margin-bottom: 0.25rem !important; }.m-sm-2 {margin: 0.5rem !important; }.mt-sm-2 {margin-top: 0.5rem !important; }.mr-sm-2 {margin-right: 0.5rem !important; }.mb-sm-2 {margin-bottom: 0.5rem !important; }.ml-sm-2 {margin-left: 0.5rem !important; }.mx-sm-2 {margin-right: 0.5rem !important;margin-left: 0.5rem !important; }.my-sm-2 {margin-top: 0.5rem !important;margin-bottom: 0.5rem !important; }.m-sm-3 {margin: 1rem !important; }.mt-sm-3 {margin-top: 1rem !important; }.mr-sm-3 {margin-right: 1rem !important; }.mb-sm-3 {margin-bottom: 1rem !important; }.ml-sm-3 {margin-left: 1rem !important; }.mx-sm-3 {margin-right: 1rem !important;margin-left: 1rem !important; }.my-sm-3 {margin-top: 1rem !important;margin-bottom: 1rem !important; }.m-sm-4 {margin: 1.5rem !important; }.mt-sm-4 {margin-top: 1.5rem !important; }.mr-sm-4 {margin-right: 1.5rem !important; }.mb-sm-4 {margin-bottom: 1.5rem !important; }.ml-sm-4 {margin-left: 1.5rem !important; }.mx-sm-4 {margin-right: 1.5rem !important;margin-left: 1.5rem !important; }.my-sm-4 {margin-top: 1.5rem !important;margin-bottom: 1.5rem !important; }.m-sm-5 {margin: 3rem !important; }.mt-sm-5 {margin-top: 3rem !important; }.mr-sm-5 {margin-right: 3rem !important; }.mb-sm-5 {margin-bottom: 3rem !important; }.ml-sm-5 {margin-left: 3rem !important; }.mx-sm-5 {margin-right: 3rem !important;margin-left: 3rem !important; }.my-sm-5 {margin-top: 3rem !important;margin-bottom: 3rem !important; }.p-sm-0 {padding: 0 !important; }.pt-sm-0 {padding-top: 0 !important; }.pr-sm-0 {padding-right: 0 !important; }.pb-sm-0 {padding-bottom: 0 !important; }.pl-sm-0 {padding-left: 0 !important; }.px-sm-0 {padding-right: 0 !important;padding-left: 0 !important; }.py-sm-0 {padding-top: 0 !important;padding-bottom: 0 !important; }.p-sm-1 {padding: 0.25rem !important; }.pt-sm-1 {padding-top: 0.25rem !important; }.pr-sm-1 {padding-right: 0.25rem !important; }.pb-sm-1 {padding-bottom: 0.25rem !important; }.pl-sm-1 {padding-left: 0.25rem !important; }.px-sm-1 {padding-right: 0.25rem !important;padding-left: 0.25rem !important; }.py-sm-1 {padding-top: 0.25rem !important;padding-bottom: 0.25rem !important; }.p-sm-2 {padding: 0.5rem !important; }.pt-sm-2 {padding-top: 0.5rem !important; }.pr-sm-2 {padding-right: 0.5rem !important; }.pb-sm-2 {padding-bottom: 0.5rem !important; }.pl-sm-2 {padding-left: 0.5rem !important; }.px-sm-2 {padding-right: 0.5rem !important;padding-left: 0.5rem !important; }.py-sm-2 {padding-top: 0.5rem !important;padding-bottom: 0.5rem !important; }.p-sm-3 {padding: 1rem !important; }.pt-sm-3 {padding-top: 1rem !important; }.pr-sm-3 {padding-right: 1rem !important; }.pb-sm-3 {padding-bottom: 1rem !important; }.pl-sm-3 {padding-left: 1rem !important; }.px-sm-3 {padding-right: 1rem !important;padding-left: 1rem !important; }.py-sm-3 {padding-top: 1rem !important;padding-bottom: 1rem !important; }.p-sm-4 {padding: 1.5rem !important; }.pt-sm-4 {padding-top: 1.5rem !important; }.pr-sm-4 {padding-right: 1.5rem !important; }.pb-sm-4 {padding-bottom: 1.5rem !important; }.pl-sm-4 {padding-left: 1.5rem !important; }.px-sm-4 {padding-right: 1.5rem !important;padding-left: 1.5rem !important; }.py-sm-4 {padding-top: 1.5rem !important;padding-bottom: 1.5rem !important; }.p-sm-5 {padding: 3rem !important; }.pt-sm-5 {padding-top: 3rem !important; }.pr-sm-5 {padding-right: 3rem !important; }.pb-sm-5 {padding-bottom: 3rem !important; }.pl-sm-5 {padding-left: 3rem !important; }.px-sm-5 {padding-right: 3rem !important;padding-left: 3rem !important; }.py-sm-5 {padding-top: 3rem !important;padding-bottom: 3rem !important; }.m-sm-auto {margin: auto !important; }.mt-sm-auto {margin-top: auto !important; }.mr-sm-auto {margin-right: auto !important; }.mb-sm-auto {margin-bottom: auto !important; }.ml-sm-auto {margin-left: auto !important; }.mx-sm-auto {margin-right: auto !important;margin-left: auto !important; }.my-sm-auto {margin-top: auto !important;margin-bottom: auto !important; } }@media (min-width: 768px) {.m-md-0 {margin: 0 !important; }.mt-md-0 {margin-top: 0 !important; }.mr-md-0 {margin-right: 0 !important; }.mb-md-0 {margin-bottom: 0 !important; }.ml-md-0 {margin-left: 0 !important; }.mx-md-0 {margin-right: 0 !important;margin-left: 0 !important; }.my-md-0 {margin-top: 0 !important;margin-bottom: 0 !important; }.m-md-1 {margin: 0.25rem !important; }.mt-md-1 {margin-top: 0.25rem !important; }.mr-md-1 {margin-right: 0.25rem !important; }.mb-md-1 {margin-bottom: 0.25rem !important; }.ml-md-1 {margin-left: 0.25rem !important; }.mx-md-1 {margin-right: 0.25rem !important;margin-left: 0.25rem !important; }.my-md-1 {margin-top: 0.25rem !important;margin-bottom: 0.25rem !important; }.m-md-2 {margin: 0.5rem !important; }.mt-md-2 {margin-top: 0.5rem !important; }.mr-md-2 {margin-right: 0.5rem !important; }.mb-md-2 {margin-bottom: 0.5rem !important; }.ml-md-2 {margin-left: 0.5rem !important; }.mx-md-2 {margin-right: 0.5rem !important;margin-left: 0.5rem !important; }.my-md-2 {margin-top: 0.5rem !important;margin-bottom: 0.5rem !important; }.m-md-3 {margin: 1rem !important; }.mt-md-3 {margin-top: 1rem !important; }.mr-md-3 {margin-right: 1rem !important; }.mb-md-3 {margin-bottom: 1rem !important; }.ml-md-3 {margin-left: 1rem !important; }.mx-md-3 {margin-right: 1rem !important;margin-left: 1rem !important; }.my-md-3 {margin-top: 1rem !important;margin-bottom: 1rem !important; }.m-md-4 {margin: 1.5rem !important; }.mt-md-4 {margin-top: 1.5rem !important; }.mr-md-4 {margin-right: 1.5rem !important; }.mb-md-4 {margin-bottom: 1.5rem !important; }.ml-md-4 {margin-left: 1.5rem !important; }.mx-md-4 {margin-right: 1.5rem !important;margin-left: 1.5rem !important; }.my-md-4 {margin-top: 1.5rem !important;margin-bottom: 1.5rem !important; }.m-md-5 {margin: 3rem !important; }.mt-md-5 {margin-top: 3rem !important; }.mr-md-5 {margin-right: 3rem !important; }.mb-md-5 {margin-bottom: 3rem !important; }.ml-md-5 {margin-left: 3rem !important; }.mx-md-5 {margin-right: 3rem !important;margin-left: 3rem !important; }.my-md-5 {margin-top: 3rem !important;margin-bottom: 3rem !important; }.p-md-0 {padding: 0 !important; }.pt-md-0 {padding-top: 0 !important; }.pr-md-0 {padding-right: 0 !important; }.pb-md-0 {padding-bottom: 0 !important; }.pl-md-0 {padding-left: 0 !important; }.px-md-0 {padding-right: 0 !important;padding-left: 0 !important; }.py-md-0 {padding-top: 0 !important;padding-bottom: 0 !important; }.p-md-1 {padding: 0.25rem !important; }.pt-md-1 {padding-top: 0.25rem !important; }.pr-md-1 {padding-right: 0.25rem !important; }.pb-md-1 {padding-bottom: 0.25rem !important; }.pl-md-1 {padding-left: 0.25rem !important; }.px-md-1 {padding-right: 0.25rem !important;padding-left: 0.25rem !important; }.py-md-1 {padding-top: 0.25rem !important;padding-bottom: 0.25rem !important; }.p-md-2 {padding: 0.5rem !important; }.pt-md-2 {padding-top: 0.5rem !important; }.pr-md-2 {padding-right: 0.5rem !important; }.pb-md-2 {padding-bottom: 0.5rem !important; }.pl-md-2 {padding-left: 0.5rem !important; }.px-md-2 {padding-right: 0.5rem !important;padding-left: 0.5rem !important; }.py-md-2 {padding-top: 0.5rem !important;padding-bottom: 0.5rem !important; }.p-md-3 {padding: 1rem !important; }.pt-md-3 {padding-top: 1rem !important; }.pr-md-3 {padding-right: 1rem !important; }.pb-md-3 {padding-bottom: 1rem !important; }.pl-md-3 {padding-left: 1rem !important; }.px-md-3 {padding-right: 1rem !important;padding-left: 1rem !important; }.py-md-3 {padding-top: 1rem !important;padding-bottom: 1rem !important; }.p-md-4 {padding: 1.5rem !important; }.pt-md-4 {padding-top: 1.5rem !important; }.pr-md-4 {padding-right: 1.5rem !important; }.pb-md-4 {padding-bottom: 1.5rem !important; }.pl-md-4 {padding-left: 1.5rem !important; }.px-md-4 {padding-right: 1.5rem !important;padding-left: 1.5rem !important; }.py-md-4 {padding-top: 1.5rem !important;padding-bottom: 1.5rem !important; }.p-md-5 {padding: 3rem !important; }.pt-md-5 {padding-top: 3rem !important; }.pr-md-5 {padding-right: 3rem !important; }.pb-md-5 {padding-bottom: 3rem !important; }.pl-md-5 {padding-left: 3rem !important; }.px-md-5 {padding-right: 3rem !important;padding-left: 3rem !important; }.py-md-5 {padding-top: 3rem !important;padding-bottom: 3rem !important; }.m-md-auto {margin: auto !important; }.mt-md-auto {margin-top: auto !important; }.mr-md-auto {margin-right: auto !important; }.mb-md-auto {margin-bottom: auto !important; }.ml-md-auto {margin-left: auto !important; }.mx-md-auto {margin-right: auto !important;margin-left: auto !important; }.my-md-auto {margin-top: auto !important;margin-bottom: auto !important; } }@media (min-width: 992px) {.m-lg-0 {margin: 0 !important; }.mt-lg-0 {margin-top: 0 !important; }.mr-lg-0 {margin-right: 0 !important; }.mb-lg-0 {margin-bottom: 0 !important; }.ml-lg-0 {margin-left: 0 !important; }.mx-lg-0 {margin-right: 0 !important;margin-left: 0 !important; }.my-lg-0 {margin-top: 0 !important;margin-bottom: 0 !important; }.m-lg-1 {margin: 0.25rem !important; }.mt-lg-1 {margin-top: 0.25rem !important; }.mr-lg-1 {margin-right: 0.25rem !important; }.mb-lg-1 {margin-bottom: 0.25rem !important; }.ml-lg-1 {margin-left: 0.25rem !important; }.mx-lg-1 {margin-right: 0.25rem !important;margin-left: 0.25rem !important; }.my-lg-1 {margin-top: 0.25rem !important;margin-bottom: 0.25rem !important; }.m-lg-2 {margin: 0.5rem !important; }.mt-lg-2 {margin-top: 0.5rem !important; }.mr-lg-2 {margin-right: 0.5rem !important; }.mb-lg-2 {margin-bottom: 0.5rem !important; }.ml-lg-2 {margin-left: 0.5rem !important; }.mx-lg-2 {margin-right: 0.5rem !important;margin-left: 0.5rem !important; }.my-lg-2 {margin-top: 0.5rem !important;margin-bottom: 0.5rem !important; }.m-lg-3 {margin: 1rem !important; }.mt-lg-3 {margin-top: 1rem !important; }.mr-lg-3 {margin-right: 1rem !important; }.mb-lg-3 {margin-bottom: 1rem !important; }.ml-lg-3 {margin-left: 1rem !important; }.mx-lg-3 {margin-right: 1rem !important;margin-left: 1rem !important; }.my-lg-3 {margin-top: 1rem !important;margin-bottom: 1rem !important; }.m-lg-4 {margin: 1.5rem !important; }.mt-lg-4 {margin-top: 1.5rem !important; }.mr-lg-4 {margin-right: 1.5rem !important; }.mb-lg-4 {margin-bottom: 1.5rem !important; }.ml-lg-4 {margin-left: 1.5rem !important; }.mx-lg-4 {margin-right: 1.5rem !important;margin-left: 1.5rem !important; }.my-lg-4 {margin-top: 1.5rem !important;margin-bottom: 1.5rem !important; }.m-lg-5 {margin: 3rem !important; }.mt-lg-5 {margin-top: 3rem !important; }.mr-lg-5 {margin-right: 3rem !important; }.mb-lg-5 {margin-bottom: 3rem !important; }.ml-lg-5 {margin-left: 3rem !important; }.mx-lg-5 {margin-right: 3rem !important;margin-left: 3rem !important; }.my-lg-5 {margin-top: 3rem !important;margin-bottom: 3rem !important; }.p-lg-0 {padding: 0 !important; }.pt-lg-0 {padding-top: 0 !important; }.pr-lg-0 {padding-right: 0 !important; }.pb-lg-0 {padding-bottom: 0 !important; }.pl-lg-0 {padding-left: 0 !important; }.px-lg-0 {padding-right: 0 !important;padding-left: 0 !important; }.py-lg-0 {padding-top: 0 !important;padding-bottom: 0 !important; }.p-lg-1 {padding: 0.25rem !important; }.pt-lg-1 {padding-top: 0.25rem !important; }.pr-lg-1 {padding-right: 0.25rem !important; }.pb-lg-1 {padding-bottom: 0.25rem !important; }.pl-lg-1 {padding-left: 0.25rem !important; }.px-lg-1 {padding-right: 0.25rem !important;padding-left: 0.25rem !important; }.py-lg-1 {padding-top: 0.25rem !important;padding-bottom: 0.25rem !important; }.p-lg-2 {padding: 0.5rem !important; }.pt-lg-2 {padding-top: 0.5rem !important; }.pr-lg-2 {padding-right: 0.5rem !important; }.pb-lg-2 {padding-bottom: 0.5rem !important; }.pl-lg-2 {padding-left: 0.5rem !important; }.px-lg-2 {padding-right: 0.5rem !important;padding-left: 0.5rem !important; }.py-lg-2 {padding-top: 0.5rem !important;padding-bottom: 0.5rem !important; }.p-lg-3 {padding: 1rem !important; }.pt-lg-3 {padding-top: 1rem !important; }.pr-lg-3 {padding-right: 1rem !important; }.pb-lg-3 {padding-bottom: 1rem !important; }.pl-lg-3 {padding-left: 1rem !important; }.px-lg-3 {padding-right: 1rem !important;padding-left: 1rem !important; }.py-lg-3 {padding-top: 1rem !important;padding-bottom: 1rem !important; }.p-lg-4 {padding: 1.5rem !important; }.pt-lg-4 {padding-top: 1.5rem !important; }.pr-lg-4 {padding-right: 1.5rem !important; }.pb-lg-4 {padding-bottom: 1.5rem !important; }.pl-lg-4 {padding-left: 1.5rem !important; }.px-lg-4 {padding-right: 1.5rem !important;padding-left: 1.5rem !important; }.py-lg-4 {padding-top: 1.5rem !important;padding-bottom: 1.5rem !important; }.p-lg-5 {padding: 3rem !important; }.pt-lg-5 {padding-top: 3rem !important; }.pr-lg-5 {padding-right: 3rem !important; }.pb-lg-5 {padding-bottom: 3rem !important; }.pl-lg-5 {padding-left: 3rem !important; }.px-lg-5 {padding-right: 3rem !important;padding-left: 3rem !important; }.py-lg-5 {padding-top: 3rem !important;padding-bottom: 3rem !important; }.m-lg-auto {margin: auto !important; }.mt-lg-auto {margin-top: auto !important; }.mr-lg-auto {margin-right: auto !important; }.mb-lg-auto {margin-bottom: auto !important; }.ml-lg-auto {margin-left: auto !important; }.mx-lg-auto {margin-right: auto !important;margin-left: auto !important; }.my-lg-auto {margin-top: auto !important;margin-bottom: auto !important; } }@media (min-width: 1200px) {.m-xl-0 {margin: 0 !important; }.mt-xl-0 {margin-top: 0 !important; }.mr-xl-0 {margin-right: 0 !important; }.mb-xl-0 {margin-bottom: 0 !important; }.ml-xl-0 {margin-left: 0 !important; }.mx-xl-0 {margin-right: 0 !important;margin-left: 0 !important; }.my-xl-0 {margin-top: 0 !important;margin-bottom: 0 !important; }.m-xl-1 {margin: 0.25rem !important; }.mt-xl-1 {margin-top: 0.25rem !important; }.mr-xl-1 {margin-right: 0.25rem !important; }.mb-xl-1 {margin-bottom: 0.25rem !important; }.ml-xl-1 {margin-left: 0.25rem !important; }.mx-xl-1 {margin-right: 0.25rem !important;margin-left: 0.25rem !important; }.my-xl-1 {margin-top: 0.25rem !important;margin-bottom: 0.25rem !important; }.m-xl-2 {margin: 0.5rem !important; }.mt-xl-2 {margin-top: 0.5rem !important; }.mr-xl-2 {margin-right: 0.5rem !important; }.mb-xl-2 {margin-bottom: 0.5rem !important; }.ml-xl-2 {margin-left: 0.5rem !important; }.mx-xl-2 {margin-right: 0.5rem !important;margin-left: 0.5rem !important; }.my-xl-2 {margin-top: 0.5rem !important;margin-bottom: 0.5rem !important; }.m-xl-3 {margin: 1rem !important; }.mt-xl-3 {margin-top: 1rem !important; }.mr-xl-3 {margin-right: 1rem !important; }.mb-xl-3 {margin-bottom: 1rem !important; }.ml-xl-3 {margin-left: 1rem !important; }.mx-xl-3 {margin-right: 1rem !important;margin-left: 1rem !important; }.my-xl-3 {margin-top: 1rem !important;margin-bottom: 1rem !important; }.m-xl-4 {margin: 1.5rem !important; }.mt-xl-4 {margin-top: 1.5rem !important; }.mr-xl-4 {margin-right: 1.5rem !important; }.mb-xl-4 {margin-bottom: 1.5rem !important; }.ml-xl-4 {margin-left: 1.5rem !important; }.mx-xl-4 {margin-right: 1.5rem !important;margin-left: 1.5rem !important; }.my-xl-4 {margin-top: 1.5rem !important;margin-bottom: 1.5rem !important; }.m-xl-5 {margin: 3rem !important; }.mt-xl-5 {margin-top: 3rem !important; }.mr-xl-5 {margin-right: 3rem !important; }.mb-xl-5 {margin-bottom: 3rem !important; }.ml-xl-5 {margin-left: 3rem !important; }.mx-xl-5 {margin-right: 3rem !important;margin-left: 3rem !important; }.my-xl-5 {margin-top: 3rem !important;margin-bottom: 3rem !important; }.p-xl-0 {padding: 0 !important; }.pt-xl-0 {padding-top: 0 !important; }.pr-xl-0 {padding-right: 0 !important; }.pb-xl-0 {padding-bottom: 0 !important; }.pl-xl-0 {padding-left: 0 !important; }.px-xl-0 {padding-right: 0 !important;padding-left: 0 !important; }.py-xl-0 {padding-top: 0 !important;padding-bottom: 0 !important; }.p-xl-1 {padding: 0.25rem !important; }.pt-xl-1 {padding-top: 0.25rem !important; }.pr-xl-1 {padding-right: 0.25rem !important; }.pb-xl-1 {padding-bottom: 0.25rem !important; }.pl-xl-1 {padding-left: 0.25rem !important; }.px-xl-1 {padding-right: 0.25rem !important;padding-left: 0.25rem !important; }.py-xl-1 {padding-top: 0.25rem !important;padding-bottom: 0.25rem !important; }.p-xl-2 {padding: 0.5rem !important; }.pt-xl-2 {padding-top: 0.5rem !important; }.pr-xl-2 {padding-right: 0.5rem !important; }.pb-xl-2 {padding-bottom: 0.5rem !important; }.pl-xl-2 {padding-left: 0.5rem !important; }.px-xl-2 {padding-right: 0.5rem !important;padding-left: 0.5rem !important; }.py-xl-2 {padding-top: 0.5rem !important;padding-bottom: 0.5rem !important; }.p-xl-3 {padding: 1rem !important; }.pt-xl-3 {padding-top: 1rem !important; }.pr-xl-3 {padding-right: 1rem !important; }.pb-xl-3 {padding-bottom: 1rem !important; }.pl-xl-3 {padding-left: 1rem !important; }.px-xl-3 {padding-right: 1rem !important;padding-left: 1rem !important; }.py-xl-3 {padding-top: 1rem !important;padding-bottom: 1rem !important; }.p-xl-4 {padding: 1.5rem !important; }.pt-xl-4 {padding-top: 1.5rem !important; }.pr-xl-4 {padding-right: 1.5rem !important; }.pb-xl-4 {padding-bottom: 1.5rem !important; }.pl-xl-4 {padding-left: 1.5rem !important; }.px-xl-4 {padding-right: 1.5rem !important;padding-left: 1.5rem !important; }.py-xl-4 {padding-top: 1.5rem !important;padding-bottom: 1.5rem !important; }.p-xl-5 {padding: 3rem !important; }.pt-xl-5 {padding-top: 3rem !important; }.pr-xl-5 {padding-right: 3rem !important; }.pb-xl-5 {padding-bottom: 3rem !important; }.pl-xl-5 {padding-left: 3rem !important; }.px-xl-5 {padding-right: 3rem !important;padding-left: 3rem !important; }.py-xl-5 {padding-top: 3rem !important;padding-bottom: 3rem !important; }.m-xl-auto {margin: auto !important; }.mt-xl-auto {margin-top: auto !important; }.mr-xl-auto {margin-right: auto !important; }.mb-xl-auto {margin-bottom: auto !important; }.ml-xl-auto {margin-left: auto !important; }.mx-xl-auto {margin-right: auto !important;margin-left: auto !important; }.my-xl-auto {margin-top: auto !important;margin-bottom: auto !important; } }.text-justify {text-align: justify !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-normal {font-weight: normal; }.font-weight-bold {font-weight: bold; }.font-italic {font-style: italic; }.text-white {color: #fff !important; }.text-primary {color: #00a4dc !important; }a.text-primary:focus, a.text-primary:hover {color: #007ea9 !important; }.text-secondary {color: #303030 !important; }a.text-secondary:focus, a.text-secondary:hover {color: #171717 !important; }.text-success {color: #28a745 !important; }a.text-success:focus, a.text-success:hover {color: #1e7e34 !important; }.text-info {color: #17a2b8 !important; }a.text-info:focus, a.text-info:hover {color: #117a8b !important; }.text-warning {color: #ffc107 !important; }a.text-warning:focus, a.text-warning:hover {color: #d39e00 !important; }.text-danger {color: #dc3545 !important; }a.text-danger:focus, a.text-danger:hover {color: #bd2130 !important; }.text-light {color: #f8f9fa !important; }a.text-light:focus, a.text-light:hover {color: #dae0e5 !important; }.text-dark {color: #343a40 !important; }a.text-dark:focus, a.text-dark:hover {color: #1d2124 !important; }.text-muted {color: #303030 !important; }.text-hide {font: 0/0 a;color: transparent;text-shadow: none;background-color: transparent;border: 0; }.visible {visibility: visible !important; }.invisible {visibility: hidden !important; }.swatch-blue {background-color: #00a4dc;color: #fff; }.swatch-indigo {background-color: #6610f2;color: #fff; }.swatch-purple {background-color: #6f42c1;color: #fff; }.swatch-pink {background-color: #e83e8c;color: #fff; }.swatch-red {background-color: #dc3545;color: #fff; }.swatch-orange {background-color: #fd7e14;color: #111; }.swatch-yellow {background-color: #ffc107;color: #111; }.swatch-green {background-color: #28a745;color: #fff; }.swatch-teal {background-color: #20c997;color: #fff; }.swatch-cyan {background-color: #17a2b8;color: #fff; }.swatch-white {background-color: #fff;color: #111; }.swatch-gray {background-color: #303030;color: #fff; }.swatch-gray-dark {background-color: #343a40;color: #fff; }.swatch-primary {background-color: #00a4dc;color: #fff; }.swatch-secondary {background-color: #303030;color: #fff; }.swatch-success {background-color: #28a745;color: #fff; }.swatch-info {background-color: #17a2b8;color: #fff; }.swatch-warning {background-color: #ffc107;color: #111; }.swatch-danger {background-color: #dc3545;color: #fff; }.swatch-light {background-color: #f8f9fa;color: #111; }.swatch-dark {background-color: #343a40;color: #fff; }.swatch-100 {background-color: #f8f9fa;color: #111; }.swatch-200 {background-color: #e9ecef;color: #111; }.swatch-300 {background-color: #dee2e6;color: #111; }.swatch-400 {background-color: #ced4da;color: #111; }.swatch-500 {background-color: #adb5bd;color: #111; }.swatch-600 {background-color: #303030;color: #fff; }.swatch-700 {background-color: #495057;color: #fff; }.swatch-800 {background-color: #343a40;color: #fff; }.swatch-900 {background-color: #212529;color: #fff; } \ No newline at end of file diff --git a/data/static/browserconfig.xml b/jellyfin_accounts/data/static/browserconfig.xml similarity index 100% rename from data/static/browserconfig.xml rename to jellyfin_accounts/data/static/browserconfig.xml diff --git a/data/static/favicon-16x16.png b/jellyfin_accounts/data/static/favicon-16x16.png similarity index 100% rename from data/static/favicon-16x16.png rename to jellyfin_accounts/data/static/favicon-16x16.png diff --git a/data/static/favicon-32x32.png b/jellyfin_accounts/data/static/favicon-32x32.png similarity index 100% rename from data/static/favicon-32x32.png rename to jellyfin_accounts/data/static/favicon-32x32.png diff --git a/data/static/favicon.ico b/jellyfin_accounts/data/static/favicon.ico similarity index 100% rename from data/static/favicon.ico rename to jellyfin_accounts/data/static/favicon.ico diff --git a/data/static/mstile-150x150.png b/jellyfin_accounts/data/static/mstile-150x150.png similarity index 100% rename from data/static/mstile-150x150.png rename to jellyfin_accounts/data/static/mstile-150x150.png diff --git a/data/static/safari-pinned-tab.svg b/jellyfin_accounts/data/static/safari-pinned-tab.svg similarity index 100% rename from data/static/safari-pinned-tab.svg rename to jellyfin_accounts/data/static/safari-pinned-tab.svg diff --git a/data/static/setup.js b/jellyfin_accounts/data/static/setup.js similarity index 100% rename from data/static/setup.js rename to jellyfin_accounts/data/static/setup.js diff --git a/data/static/site.webmanifest b/jellyfin_accounts/data/static/site.webmanifest similarity index 100% rename from data/static/site.webmanifest rename to jellyfin_accounts/data/static/site.webmanifest diff --git a/data/templates/404.html b/jellyfin_accounts/data/templates/404.html similarity index 100% rename from data/templates/404.html rename to jellyfin_accounts/data/templates/404.html diff --git a/data/templates/admin.html b/jellyfin_accounts/data/templates/admin.html similarity index 100% rename from data/templates/admin.html rename to jellyfin_accounts/data/templates/admin.html diff --git a/data/templates/form.html b/jellyfin_accounts/data/templates/form.html similarity index 100% rename from data/templates/form.html rename to jellyfin_accounts/data/templates/form.html diff --git a/data/templates/invalidCode.html b/jellyfin_accounts/data/templates/invalidCode.html similarity index 100% rename from data/templates/invalidCode.html rename to jellyfin_accounts/data/templates/invalidCode.html diff --git a/data/templates/setup.html b/jellyfin_accounts/data/templates/setup.html similarity index 100% rename from data/templates/setup.html rename to jellyfin_accounts/data/templates/setup.html diff --git a/jellyfin_accounts/email.py b/jellyfin_accounts/email.py index d69e8d2..e5ab2b2 100644 --- a/jellyfin_accounts/email.py +++ b/jellyfin_accounts/email.py @@ -8,8 +8,8 @@ from email.mime.multipart import MIMEMultipart from pathlib import Path from dateutil import parser as date_parser from jinja2 import Environment, FileSystemLoader -from __main__ import config -from __main__ import email_log as log +from jellyfin_accounts import config +from jellyfin_accounts import email_log as log class Email(): diff --git a/jellyfin_accounts/login.py b/jellyfin_accounts/login.py index d57f8a5..e6dd78e 100644 --- a/jellyfin_accounts/login.py +++ b/jellyfin_accounts/login.py @@ -6,8 +6,8 @@ from itsdangerous import (TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired) from passlib.apps import custom_app_context as pwd_context import uuid -from __main__ import config, app, g -from __main__ import auth_log as log +from jellyfin_accounts import config, app, g +from jellyfin_accounts import auth_log as log from jellyfin_accounts.jf_api import Jellyfin from jellyfin_accounts.web_api import jf diff --git a/jellyfin_accounts/pw_reset.py b/jellyfin_accounts/pw_reset.py index 4b6c664..1403924 100755 --- a/jellyfin_accounts/pw_reset.py +++ b/jellyfin_accounts/pw_reset.py @@ -4,8 +4,8 @@ from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from jellyfin_accounts.email import Mailgun, Smtp from jellyfin_accounts.web_api import jf -from __main__ import config, data_store -from __main__ import email_log as log +from jellyfin_accounts import config, data_store +from jellyfin_accounts import email_log as log diff --git a/jellyfin_accounts/setup.py b/jellyfin_accounts/setup.py index 9a2c273..6ddcfd1 100644 --- a/jellyfin_accounts/setup.py +++ b/jellyfin_accounts/setup.py @@ -1,8 +1,8 @@ from flask import request, jsonify, render_template from configparser import RawConfigParser from jellyfin_accounts.jf_api import Jellyfin -from __main__ import config, config_path, app, first_run -from __main__ import web_log as log +from jellyfin_accounts import config, config_path, app, first_run +from jellyfin_accounts import web_log as log import os if first_run: diff --git a/jellyfin_accounts/web.py b/jellyfin_accounts/web.py index 8907a6b..6d7bcf1 100644 --- a/jellyfin_accounts/web.py +++ b/jellyfin_accounts/web.py @@ -1,8 +1,8 @@ import json from pathlib import Path from flask import Flask, send_from_directory, render_template -from __main__ import config, app, g, css, data_store -from __main__ import web_log as log +from jellyfin_accounts import config, app, g, css, data_store +from jellyfin_accounts import web_log as log from jellyfin_accounts.web_api import checkInvite, validator diff --git a/jellyfin_accounts/web_api.py b/jellyfin_accounts/web_api.py index eb34265..c1e6248 100644 --- a/jellyfin_accounts/web_api.py +++ b/jellyfin_accounts/web_api.py @@ -4,8 +4,8 @@ import json import datetime import secrets import time -from __main__ import config, config_path, app, g, data_store -from __main__ import web_log as log +from jellyfin_accounts import config, config_path, app, g, data_store +from jellyfin_accounts import web_log as log from jellyfin_accounts.validate_password import PasswordValidator def resp(success=True, code=500): diff --git a/jf-accounts b/jf-accounts deleted file mode 100755 index 5417933..0000000 --- a/jf-accounts +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/env python3 -import secrets -import configparser -import shutil -import argparse -import logging -import threading -import signal -import sys -import json -from pathlib import Path -from flask import Flask, g -from jellyfin_accounts.data_store import JSONStorage - -parser = argparse.ArgumentParser(description="jellyfin-accounts") - -parser.add_argument("-c", "--config", - help="specifies path to configuration file.") -parser.add_argument("-d", "--data", - help=("specifies directory to store data in. " + - "defaults to ~/.jf-accounts.")) -parser.add_argument("--host", - help="address to host web ui on.") -parser.add_argument("-p", "--port", - help="port to host web ui on.") -parser.add_argument("-g", "--get_defaults", - help=("tool to grab a JF users " + - "policy (access, perms, etc.) and " + - "homescreen layout and " + - "output it as json to be used as a user template."), - action='store_true') - -args, leftovers = parser.parse_known_args() - -if args.data is not None: - data_dir = Path(args.data) -else: - data_dir = Path.home() / '.jf-accounts' - -local_dir = (Path(__file__).parents[2] / 'data').resolve() - -first_run = False -if data_dir.exists() is False or (data_dir / 'config.ini').exists() is False: - if not data_dir.exists(): - Path.mkdir(data_dir) - print(f'Config dir not found, so created at {str(data_dir)}') - if args.config is None: - config_path = data_dir / 'config.ini' - shutil.copy(str(local_dir / 'config-default.ini'), - str(config_path)) - print("Setup through the web UI, or quit and edit the configuration manually.") - first_run = True - else: - config_path = Path(args.config) - print(f'config.ini can be found at {str(config_path)}') -else: - config_path = data_dir / 'config.ini' - -config = configparser.RawConfigParser() -config.read(config_path) - -def create_log(name): - log = logging.getLogger(name) - handler = logging.StreamHandler(sys.stdout) - if config.getboolean('ui', 'debug'): - log.setLevel(logging.DEBUG) - handler.setLevel(logging.DEBUG) - else: - log.setLevel(logging.INFO) - handler.setLevel(logging.INFO) - fmt = ' %(name)s - %(levelname)s - %(message)s' - format = logging.Formatter(fmt) - handler.setFormatter(format) - log.addHandler(handler) - log.propagate = False - return log - -log = create_log('main') -web_log = create_log('waitress') -if not first_run: - email_log = create_log('emails') - auth_log = create_log('auth') - -if args.host is not None: - log.debug(f'Using specified host {args.host}') - config['ui']['host'] = args.host -if args.port is not None: - log.debug(f'Using specified port {args.port}') - config['ui']['port'] = args.port - -for key in config['files']: - if config['files'][key] == '': - if key != 'custom_css': - log.debug(f'Using default {key}') - config['files'][key] = str(data_dir / (key + '.json')) - -for key in ['user_configuration', 'user_displayprefs']: - if key not in config['files']: - log.debug(f'Using default {key}') - config['files'][key] = str(data_dir / (key + '.json')) - -with open(config['files']['invites'], 'r') as f: - temp_invites = json.load(f) -if 'invites' in temp_invites: - new_invites = {} - log.info('Converting invites.json to new format, temporary.') - for el in temp_invites['invites']: - i = {'valid_till': el['valid_till']} - if 'email' in el: - i['email'] = el['email'] - new_invites[el['code']] = i - with open(config['files']['invites'], 'w') as f: - f.write(json.dumps(new_invites, indent=4, default=str)) - - -data_store = JSONStorage(config['files']['emails'], - config['files']['invites'], - config['files']['user_template'], - config['files']['user_displayprefs'], - config['files']['user_configuration']) - -def default_css(): - css = {} - css['href'] = "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" - css['integrity'] = "sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" - css['crossorigin'] = "anonymous" - return css - - -css = {} -css = default_css() -if 'custom_css' in config['files']: - if config['files']['custom_css'] != '': - try: - shutil.copy(config['files']['custom_css'], - (local_dir / 'static' / 'bootstrap.css')) - log.debug('Loaded custom CSS') - css['href'] = '/bootstrap.css' - css['integrity'] = '' - css['crossorigin'] = '' - except FileNotFoundError: - log.error(f'Custom CSS {config["files"]["custom_css"]} not found, using default.') - - -if ('email_html' not in config['password_resets'] or - config['password_resets']['email_html'] == ''): - log.debug('Using default password reset email HTML template') - config['password_resets']['email_html'] = str(local_dir / 'email.html') -if ('email_text' not in config['password_resets'] or - config['password_resets']['email_text'] == ''): - log.debug('Using default password reset email plaintext template') - config['password_resets']['email_text'] = str(local_dir / 'email.txt') - -if ('email_html' not in config['invite_emails'] or - config['invite_emails']['email_html'] == ''): - log.debug('Using default invite email HTML template') - config['invite_emails']['email_html'] = str(local_dir / - 'invite-email.html') -if ('email_text' not in config['invite_emails'] or - config['invite_emails']['email_text'] == ''): - log.debug('Using default invite email plaintext template') - config['invite_emails']['email_text'] = str(local_dir / - 'invite-email.txt') -if ('public_server' not in config['jellyfin'] or - config['jellyfin']['public_server'] == ''): - config['jellyfin']['public_server'] = config['jellyfin']['server'] - -if args.get_defaults: - import json - from jellyfin_accounts.jf_api import Jellyfin - jf = Jellyfin(config['jellyfin']['server'], - config['jellyfin']['client'], - config['jellyfin']['version'], - config['jellyfin']['device'], - config['jellyfin']['device_id']) - print("NOTE: This can now be done through the web ui.") - print(""" - This tool lets you grab various settings from a user, - so that they can be applied every time a new account is - created. """) - print("Step 1: User Policy.") - print(""" - A user policy stores a users permissions (e.g access rights and - most of the other settings in the 'Profile' and 'Access' tabs - of a user). """) - success = False - msg = "Get public users only or all users? (requires auth) [public/all]: " - public = False - while not success: - choice = input(msg) - if choice == 'public': - public = True - print("Make sure the user is publicly visible!") - success = True - elif choice == 'all': - jf.authenticate(config['jellyfin']['username'], - config['jellyfin']['password']) - public = False - success = True - users = jf.getUsers(public=public) - for index, user in enumerate(users): - print(f'{index+1}) {user["Name"]}') - success = False - while not success: - try: - user_index = int(input(">: "))-1 - policy = users[user_index]['Policy'] - success = True - except (ValueError, IndexError): - pass - data_store.user_template = policy - print(f'Policy written to "{config["files"]["user_template"]}".') - print('In future, this policy will be copied to all new users.') - print('Step 2: Homescreen Layout') - print(""" - You may want to customize the default layout of a new user's - home screen. These settings can be applied to an account through - the 'Home' section in a user's settings. """) - success = False - while not success: - choice = input("Grab the chosen user's homescreen layout? [y/n]: ") - if choice.lower() == 'y': - user_id = users[user_index]['Id'] - configuration = users[user_index]['Configuration'] - display_prefs = jf.getDisplayPreferences(user_id) - data_store.user_configuration = configuration - print(f'Configuration written to "{config["files"]["user_configuration"]}".') - data_store.user_displayprefs = display_prefs - print(f'Display Prefs written to "{config["files"]["user_displayprefs"]}".') - success = True - elif choice.lower() == 'n': - success = True - -else: - def signal_handler(sig, frame): - print('Quitting...') - sys.exit(0) - - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - app = Flask(__name__, root_path=str(local_dir)) - app.config['DEBUG'] = config.getboolean('ui', 'debug') - app.config['SECRET_KEY'] = secrets.token_urlsafe(16) - - if __name__ == '__main__': - from waitress import serve - if first_run: - import jellyfin_accounts.setup - host = config['ui']['host'] - port = config['ui']['port'] - log.info('Starting web UI for first run setup...') - serve(app, - host=host, - port=port) - else: - import jellyfin_accounts.web_api - import jellyfin_accounts.web - host = config['ui']['host'] - port = config['ui']['port'] - log.info(f'Starting web UI on {host}:{port}') - if config.getboolean('password_resets', 'enabled'): - def start_pwr(): - import jellyfin_accounts.pw_reset - jellyfin_accounts.pw_reset.start() - pwr = threading.Thread(target=start_pwr, daemon=True) - log.info('Starting email thread') - pwr.start() - - serve(app, - host=host, - port=int(port)) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ca40362 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,449 @@ +[[package]] +category = "main" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +python-versions = "*" +version = "2020.4.5.2" + +[[package]] +category = "main" +description = "Foreign Function Interface for Python calling C code." +name = "cffi" +optional = false +python-versions = "*" +version = "1.14.0" + +[package.dependencies] +pycparser = "*" + +[[package]] +category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "main" +description = "Composable command line interface toolkit" +name = "click" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" + +[[package]] +category = "main" +description = "Updated configparser from Python 3.8 for Python 2.6+." +name = "configparser" +optional = false +python-versions = ">=3.6" +version = "5.0.0" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov"] + +[[package]] +category = "main" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +name = "cryptography" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "2.9.2" + +[package.dependencies] +cffi = ">=1.8,<1.11.3 || >1.11.3" +six = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +idna = ["idna (>=2.1)"] +pep8test = ["flake8", "flake8-import-order", "pep8-naming"] +test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] + +[[package]] +category = "main" +description = "A simple framework for building complex web applications." +name = "flask" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.1.2" + +[package.dependencies] +Jinja2 = ">=2.10.1" +Werkzeug = ">=0.15" +click = ">=5.1" +itsdangerous = ">=0.24" + +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +dotenv = ["python-dotenv"] + +[[package]] +category = "main" +description = "Basic and Digest HTTP authentication for Flask routes" +name = "flask-httpauth" +optional = false +python-versions = "*" +version = "4.1.0" + +[package.dependencies] +Flask = "*" + +[[package]] +category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.9" + +[[package]] +category = "main" +description = "Various helpers to pass data to untrusted environments and back." +name = "itsdangerous" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" + +[[package]] +category = "main" +description = "A very fast and expressive template engine." +name = "jinja2" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.2" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[package.extras] +i18n = ["Babel (>=0.8)"] + +[[package]] +category = "main" +description = "Safely add untrusted strings to HTML/XML markup." +name = "markupsafe" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.1.1" + +[[package]] +category = "main" +description = "comprehensive password hashing framework supporting over 30 schemes" +name = "passlib" +optional = false +python-versions = "*" +version = "1.7.2" + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.0)"] +totp = ["cryptography"] + +[[package]] +category = "main" +description = "File system general utilities" +name = "pathtools" +optional = false +python-versions = "*" +version = "0.1.2" + +[[package]] +category = "main" +description = "C parser in Python" +name = "pycparser" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" + +[[package]] +category = "main" +description = "Python wrapper module around the OpenSSL library" +name = "pyopenssl" +optional = false +python-versions = "*" +version = "19.1.0" + +[package.dependencies] +cryptography = ">=2.8" +six = ">=1.5.2" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[[package]] +category = "main" +description = "Extensions to the standard Python datetime module" +name = "python-dateutil" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +version = "2.8.1" + +[package.dependencies] +six = ">=1.5" + +[[package]] +category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2020.1" + +[[package]] +category = "main" +description = "Python HTTP for Humans." +name = "requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.23.0" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<4" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" + +[[package]] +category = "main" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.25.9" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] + +[[package]] +category = "main" +description = "Waitress WSGI server" +name = "waitress" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.4.4" + +[package.extras] +docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] +testing = ["pytest", "pytest-cover", "coverage (>=5.0)"] + +[[package]] +category = "main" +description = "Filesystem events monitoring" +name = "watchdog" +optional = false +python-versions = "*" +version = "0.10.2" + +[package.dependencies] +pathtools = ">=0.1.1" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] + +[[package]] +category = "main" +description = "The comprehensive WSGI web application library." +name = "werkzeug" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.0.1" + +[package.extras] +dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] +watchdog = ["watchdog"] + +[metadata] +content-hash = "721be13a1e348d7e424529ba8466b9e2408df2cd97ab45e7e0d2f665b3213879" +python-versions = "^3.6" + +[metadata.files] +certifi = [ + {file = "certifi-2020.4.5.2-py2.py3-none-any.whl", hash = "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"}, + {file = "certifi-2020.4.5.2.tar.gz", hash = "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1"}, +] +cffi = [ + {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, + {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, + {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, + {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, + {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, + {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, + {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, + {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, + {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, + {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, + {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, + {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, + {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, + {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, + {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, + {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +configparser = [ + {file = "configparser-5.0.0-py3-none-any.whl", hash = "sha256:cffc044844040c7ce04e9acd1838b5f2e5fa3170182f6fda4d2ea8b0099dbadd"}, + {file = "configparser-5.0.0.tar.gz", hash = "sha256:2ca44140ee259b5e3d8aaf47c79c36a7ab0d5e94d70bd4105c03ede7a20ea5a1"}, +] +cryptography = [ + {file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"}, + {file = "cryptography-2.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b"}, + {file = "cryptography-2.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365"}, + {file = "cryptography-2.9.2-cp27-cp27m-win32.whl", hash = "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0"}, + {file = "cryptography-2.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55"}, + {file = "cryptography-2.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270"}, + {file = "cryptography-2.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf"}, + {file = "cryptography-2.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d"}, + {file = "cryptography-2.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785"}, + {file = "cryptography-2.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b"}, + {file = "cryptography-2.9.2-cp35-cp35m-win32.whl", hash = "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae"}, + {file = "cryptography-2.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b"}, + {file = "cryptography-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6"}, + {file = "cryptography-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3"}, + {file = "cryptography-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b"}, + {file = "cryptography-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e"}, + {file = "cryptography-2.9.2-cp38-cp38-win32.whl", hash = "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0"}, + {file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"}, + {file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"}, +] +flask = [ + {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, + {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, +] +flask-httpauth = [ + {file = "Flask-HTTPAuth-4.1.0.tar.gz", hash = "sha256:9e028e4375039a49031eb9ecc40be4761f0540476040f6eff329a31dabd4d000"}, + {file = "Flask_HTTPAuth-4.1.0-py2.py3-none-any.whl", hash = "sha256:29e0288869a213c7387f0323b6bf2c7191584fb1da8aa024d9af118e5cd70de7"}, +] +idna = [ + {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, + {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, +] +itsdangerous = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, +] +jinja2 = [ + {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, + {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +passlib = [ + {file = "passlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177"}, + {file = "passlib-1.7.2.tar.gz", hash = "sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"}, +] +pathtools = [ + {file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pyopenssl = [ + {file = "pyOpenSSL-19.1.0-py2.py3-none-any.whl", hash = "sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504"}, + {file = "pyOpenSSL-19.1.0.tar.gz", hash = "sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] +pytz = [ + {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, + {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, +] +requests = [ + {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, + {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +urllib3 = [ + {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, + {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, +] +waitress = [ + {file = "waitress-1.4.4-py2.py3-none-any.whl", hash = "sha256:3d633e78149eb83b60a07dfabb35579c29aac2d24bb803c18b26fb2ab1a584db"}, + {file = "waitress-1.4.4.tar.gz", hash = "sha256:1bb436508a7487ac6cb097ae7a7fe5413aefca610550baf58f0940e51ecfb261"}, +] +watchdog = [ + {file = "watchdog-0.10.2.tar.gz", hash = "sha256:c560efb643faed5ef28784b2245cf8874f939569717a4a12826a173ac644456b"}, +] +werkzeug = [ + {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, + {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..46e58e9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,42 @@ +[tool.poetry] +name = "jellyfin-accounts" +version = "0.2.0" +readme = "README.md" +description = "A simple account management system for Jellyfin" +authors = ["Harvey Tindall "] +license = "MIT" +homepage = "https://github.com/hrfee/jellyfin-accounts" +repository = "https://github.com/hrfee/jellyfin-accounts" +keywords = ["jellyfin", "jf-accounts"] +include = ["jellyfin_accounts/data/*"] +exclude = ["images/*"] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + + +[tool.poetry.dependencies] +python = "^3.6" +pyopenssl = "^19.1.0" +flask = "^1.1.2" +flask-httpauth = "^3.3.0" +requests = "^2.23.0" +itsdangerous = "^1.1.0" +passlib = "^1.7.2" +pytz = "^2020.1" +python-dateutil = "^2.8.1" +watchdog = "^0.10.2" +configparser = "^5.0.0" +waitress = "^1.4.3" + +[tool.poetry.dev-dependencies] + + +[tool.poetry.scripts] +jf-accounts = 'jellyfin_accounts:main' + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a9fa3e2..0000000 --- a/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -pyOpenSSL -Flask -flask_httpauth -requests -itsdangerous -passlib -secrets -pytz -python-dateutil -watchdog -configparser -waitress diff --git a/setup.py b/setup.py deleted file mode 100644 index 10bd0db..0000000 --- a/setup.py +++ /dev/null @@ -1,62 +0,0 @@ -from setuptools import find_packages, setup - -with open('README.md', 'r') as f: - long_description = f.read() - -setup( - name='jellyfin-accounts', - version='0.1', - scripts=['jf-accounts'], - author="Harvey Tindall", - author_email="hrfee@protonmail.ch", - description="A simple invite system for Jellyfin", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/hrfee/jellyfin-accounts", - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - packages=find_packages(), - # include_package_data=True, - data_files=[('data', ['data/config-default.ini', - 'data/email.html', - 'data/email.txt', - 'data/invite-email.html', - 'data/invite-email.txt']), - ('data/static', ['data/static/admin.js', - 'data/static/setup.js', - 'data/static/apple-touch-icon.png', - 'data/static/android-chrome-192x192.png', - 'data/static/android-chrome-512x512.png', - 'data/static/favicon-16x16.png', - 'data/static/favicon-32x32.png', - 'data/static/mstile-150x150.png', - 'data/static/safari-pinned-tab.svg', - 'data/static/site.webmanifest', - 'data/static/browserconfig.xml', - 'data/static/favicon.ico']), - ('data/templates', [ - 'data/templates/404.html', - 'data/templates/invalidCode.html', - 'data/templates/admin.html', - 'data/templates/form.html', - 'data/templates/setup.html'])], - zip_safe=False, - install_requires=[ - 'pyOpenSSL', - 'Flask', - 'flask_httpauth', - 'requests', - 'itsdangerous', - 'passlib', - 'secrets', - 'pytz', - 'python-dateutil', - 'watchdog', - 'configparser', - 'waitress', - ], -) -