commit c0b4ca1021b38251efb1c00829ed87f74d336048 Author: Ana Custura Date: Tue Feb 17 08:42:33 2026 +0000 Initial commit diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..65badd7 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,23 @@ +from flask import Flask +from config import Config +from flask_sqlalchemy import SQLAlchemy +from flask_migrate import Migrate +from flask_login import LoginManager +from flask import request +from flask_babel import Babel +from flask_babel import lazy_gettext as _l + +def get_locale(): + return request.accept_languages.best_match(app.config['LANGUAGES']) +app = Flask(__name__) +babel = Babel(app, locale_selector=get_locale) +app.config.from_object(Config) +db = SQLAlchemy(app) +migrate = Migrate(app, db) +login = LoginManager(app) +login.login_view = 'login' +login.login_message = _l('Please log in to access this page.') +from app import routes, models + +from app.commands import seed_settings_command +app.cli.add_command(seed_settings_command) \ No newline at end of file diff --git a/app/commands.py b/app/commands.py new file mode 100644 index 0000000..1e02ec2 --- /dev/null +++ b/app/commands.py @@ -0,0 +1,49 @@ +import click +from app import db +from flask import current_app +from app.models import Setting, User + + +def seed_defaults(): + defaults = { + "butterbox_name": current_app.config["BUTTERBOX_NAME"], + "butterbox_logo": current_app.config["BUTTERBOX_LOGO"], + "ssid": current_app.config["BUTTERBOX_SSID"], + "wifi_password": current_app.config["BUTTERBOX_WIFI_PASSWORD"], + "disable_access_point": current_app.config["DISABLE_ACCESS_POINT"], + "apply_changes": "false", + "onboarding_complete": "false", + "lock_root_password": "false", + "disable_file_viewer": current_app.config["DISABLE_FILE_VIEWER"], + "disable_map_viewer": current_app.config["DISABLE_MAP_VIEWER"], + "disable_chat": current_app.config["DISABLE_CHAT"], + "disable_app_store": current_app.config["DISABLE_APP_STORE"], + "ssh_password": "", + "admin_password": current_app.config["ADMIN_PASSWORD"], + } + + for key, value in defaults.items(): + exists = Setting.query.filter_by(key=key).first() + if not exists: + db.session.add(Setting(key=key, value=value)) + click.echo("Created new setting {}".format(key)) + else: + click.echo("Found existing setting {}".format(key)) + db.session.commit() + + admin_user_exists = User.query.filter_by(username='admin').first() + if not admin_user_exists: + u = User(username='admin') + u.set_password('admin') + db.session.add(u) + db.session.commit() + click.echo("Created new admin user") + else: + click.echo("Found existing admin user") + + +@click.command("seed-settings") +def seed_settings_command(): + """Seed default settings into the database (only if missing).""" + seed_defaults() + click.echo("Finished seeding default settings.") diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 0000000..43548bd --- /dev/null +++ b/app/forms.py @@ -0,0 +1,32 @@ +from flask_wtf import FlaskForm +from flask_wtf.file import FileAllowed, FileRequired +from wtforms import StringField, PasswordField, SubmitField, BooleanField, FileField +from wtforms.validators import DataRequired +from flask_babel import lazy_gettext as _l + +class LoginForm(FlaskForm): + username = StringField(_l('Username'), validators=[DataRequired()]) + password = PasswordField(_l('Password'), validators=[DataRequired()]) + submit = SubmitField(_l('Sign In')) + remember_me = BooleanField('Remember Me') + + +class SettingsForm(FlaskForm): + # Access point settings + ssid = StringField('SSID', validators=[DataRequired()]) + wifi_password = PasswordField(_l('WiFi Password')) + disable_access_point = BooleanField(_l('Disable Access Point')) + # Customisation settings + butterbox_name = StringField(_l('Butterbox Name'), validators=[DataRequired()]) + butterbox_logo = FileField((_l('Butterbox Logo')), validators=[FileAllowed(['jpg', 'png', 'svg'], 'Images only!')]) + # Services settings + disable_file_viewer = BooleanField(_l('Disable File Viewer')) + disable_map_viewer = BooleanField(_l('Disable Map Viewer')) + disable_chat = BooleanField(_l('Disable Chat')) + disable_app_store = BooleanField(_l('Disable App Store')) + # Access Settings + admin_password = PasswordField(_l('Admin Password')) + ssh_password = PasswordField(_l('SSH Password')) + + submit = SubmitField(_l('Submit')) + apply_changes = SubmitField(_l('Apply Changes')) diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..c1a4d28 --- /dev/null +++ b/app/models.py @@ -0,0 +1,35 @@ +from typing import Optional +import sqlalchemy as sa +import sqlalchemy.orm as so +from app import db +from werkzeug.security import generate_password_hash, check_password_hash +from flask_login import UserMixin +from app import login + +class User(UserMixin, db.Model): + id: so.Mapped[int] = so.mapped_column(primary_key=True) + username: so.Mapped[str] = so.mapped_column(sa.String(64), index=True, + unique=True) + password_hash: so.Mapped[Optional[str]] = so.mapped_column(sa.String(256)) + + def set_password(self, password: str) -> None: + self.password_hash = generate_password_hash(password) + + def check_password(self, password: str) -> bool: + return check_password_hash(self.password_hash, password) + + def __repr__(self): + return ''.format(self.username) + +class Setting(db.Model): + id: so.Mapped[int] = so.mapped_column(primary_key=True) + key: so.Mapped[str] = so.mapped_column(sa.String(255), index=True, + unique=True, nullable=False) + value: so.Mapped[str] = so.mapped_column(sa.String(255)) + + def __repr__(self): + return ''.format(self.key) + +@login.user_loader +def load_user(user_id: str) -> User: + return db.session.get(User, int(user_id)) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000..f715551 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,140 @@ +from email.mime import image + +from alembic.util import obfuscate_url_pw + +from app import app +from flask import render_template, flash, redirect, url_for, request, session +from app.forms import LoginForm, SettingsForm +from flask_login import login_user, current_user, logout_user, login_required +import sqlalchemy as sa +from app import db +from app.models import User, Setting +from werkzeug.datastructures import FileStorage +import json +from flask_babel import _ +import base64 + +def get_setting(name) -> str: + setting = db.session.scalar(sa.select(Setting).where(Setting.key == name)) + return str(setting.value) + +def set_setting(name: str, value: str): + setting = db.session.scalar(sa.select(Setting).where(Setting.key == name)) + print(f"I have changed {setting.key}") + setting.value = value + db.session.add(setting) + +def dump_settings(filename: str) -> None: + settings = db.session.execute(sa.select(Setting)).scalars().all() + settings_dict = {s.key: s.value for s in settings} + print(settings_dict) + with open(filename, "w") as f: + json.dump(settings_dict, f, indent=4) +@app.route('/') +@app.route('/index') +def index(): + disable_chat = get_setting("disable_chat") + disable_app_store = get_setting("disable_app_store") + disable_map_viewer = get_setting("disable_map_viewer") + disable_file_viewer = get_setting("disable_file_viewer") + service_array = [] + usb_inserted = False # actual test of whether USB is inserted + usb_has_maps = False # actual test of whether USB has maps folder + usb_has_appstore = False # actual test of whether USB has an appstore + if disable_chat == 'false': + service_array.append({"name": "Message Board", "image": url_for("static", filename="images/chat-icon.png"), "url": app.config["CONVENE_INSTALL_PATH"] }) + if disable_app_store == 'false' and usb_has_appstore: + service_array.append({"name": "Apps", "image": url_for("static", filename="images/appstore-icon.svg")}) + if disable_map_viewer == 'false' and usb_has_maps: + service_array.append({"name": "Offline Maps", "image": url_for("static", filename="images/maps-icon.png")}) + if disable_file_viewer == 'false': + name = "Files" + if not usb_inserted: + name = "Insert USB to browse files" + service_array.append({ + "name": name, + "image": url_for("static", filename="images/explore-icon.svg"), + "url": url_for("usb")}) + return render_template('index.html', title='Home', get_setting=get_setting, services=service_array) + +@app.route('/usb') +def usb(): + return render_template('usb-file-viewer.html', title='File Viewer') + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('admin')) + form = LoginForm() + form.remember_me.data = False + if form.validate_on_submit(): + user = db.session.scalar(sa.select(User).where(User.username == form.username.data)) + if user is None or not user.check_password(form.password.data): + flash(_('Invalid username or password')) + return redirect(url_for('login')) + login_user(user) + return redirect(url_for('admin')) + return render_template('login.html', title='Sign in', form=form, get_setting=get_setting) + + +@app.route('/logout') +def logout(): + logout_user() + return redirect(url_for('index')) +@app.route('/admin', methods=['GET', 'POST']) +@login_required +def admin(): + form = SettingsForm() + populate_settings = ['butterbox_name', 'wifi_password', 'disable_access_point', 'ssid', 'disable_file_viewer', 'disable_map_viewer', 'disable_app_store', 'disable_chat' ] + + bool_settings = ['disable_access_point','disable_file_viewer', 'disable_map_viewer', 'disable_app_store', 'disable_chat'] + + if not form.is_submitted(): + for s in populate_settings: + if s in bool_settings: + getattr(form, s).data = (get_setting(s) == "true") + else: + getattr(form, s).data = get_setting(s) + + if form.validate_on_submit(): + if form.submit.data: + for s in populate_settings: + new_value = getattr(form, s).data + if s in bool_settings: + new_value = str(new_value).lower() # all settings are str fow now + existing_value = get_setting(s) + if new_value != existing_value: + print(f"New value was changed for {s}. Existing value was {existing_value}, new value was {new_value}") + set_setting(s, new_value) + if s in ['butterbox_name', 'wifi_password', 'ssid', 'disable_access_point']: + app.config['SETTINGS_CHANGED'] = True + + new_logo = form.butterbox_logo.data + if new_logo.filename: + logo_stream = form.butterbox_logo.data.stream + b64_logo = base64.b64encode(logo_stream.read()).decode('utf-8') + file_mimetype = form.butterbox_logo.data.mimetype + new_value = f"data:{file_mimetype};base64,{b64_logo}" + existing_value = get_setting('butterbox_logo') + if new_value != existing_value: + print( f"New value was changed for logo") + set_setting('butterbox_logo', new_value) + new_admin_password = form.admin_password.data + if new_admin_password: + existing_admin_password = get_setting('admin_password') + if new_admin_password != existing_admin_password: + print( f"New value was changed for admin password") + set_setting('admin_password', new_admin_password) + print(get_setting('admin_password')) + + if app.config['SETTINGS_CHANGED']: + flash(_("⚠️ Some settings won't take effect until the Butter Box restarts. Click 'Apply Changes' to restart.")) + db.session.commit() + + if form.apply_changes.data: + set_setting('apply_changes', "true") + dump_settings("settings.txt") + flash(_("⚠️ Changes applied! Please wait for the box to restart.")) + + + return render_template('admin.html', get_setting=get_setting, form=form) diff --git a/app/static/butter_styles.css b/app/static/butter_styles.css new file mode 100644 index 0000000..c45feec --- /dev/null +++ b/app/static/butter_styles.css @@ -0,0 +1,21 @@ +.butter-title { + text-align: center; +} + +.butter-service { + border-radius: 20px; +} + +.butter-service__image { + margin: 0 auto; +} + +.butter-service__content { + display: block; +} + +@media (max-width: 960px) { + html { + padding: 10px; + } +} \ No newline at end of file diff --git a/app/static/images/appstore-icon.svg b/app/static/images/appstore-icon.svg new file mode 100644 index 0000000..df8a694 --- /dev/null +++ b/app/static/images/appstore-icon.svg @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/static/images/chat-icon.png b/app/static/images/chat-icon.png new file mode 100644 index 0000000..b14bdd4 Binary files /dev/null and b/app/static/images/chat-icon.png differ diff --git a/app/static/images/explore-icon.svg b/app/static/images/explore-icon.svg new file mode 100644 index 0000000..3354fbe --- /dev/null +++ b/app/static/images/explore-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/static/images/maps-icon.png b/app/static/images/maps-icon.png new file mode 100644 index 0000000..b294641 Binary files /dev/null and b/app/static/images/maps-icon.png differ diff --git a/app/templates/admin.html b/app/templates/admin.html new file mode 100644 index 0000000..2ed362e --- /dev/null +++ b/app/templates/admin.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} + +{% block content %} +

{{ _('Application Settings') }}

+{% import "bulma_wtf.html" as wtf %} +
+ {{ form.hidden_tag() }} + {% if config['SETTINGS_CHANGED'] %} +

{{ form.apply_changes(class="button is-warning") }}

+ {% endif %} +
+ {{ wtf.form_input_field(form.ssid) }} +

This is the name of the advertised Wi-Fi network. Current SSID: {{ get_setting('ssid') }}

+
+
+ {{ wtf.form_input_field(form.wifi_password) }} +

This is the secret key needed to connect to the Wi-Fi network. By default, this is not set and everyone can join. + Current password: {{ get_setting('wifi_password') or 'Not set' }}

+
+
+ {{ wtf.form_input_field(form.butterbox_name) }} +

This is the name shown in the UI, and used to access the box locally by adding .local or .lan in your browser. + Current name: {{ get_setting('butterbox_name') }}, accessed at {{ get_setting('butterbox_name') }}.local.

+
+
+ {{ wtf.form_bool_field(form.disable_access_point) }} +

Whether this box will advertise a WiFi network.

+
+
+ {{ wtf.form_bool_field(form.disable_map_viewer) }} +

Whether map services are enabled.

+
+
+ {{ wtf.form_bool_field(form.disable_chat) }} +

Whether chat services are enabled.

+
+
+ {{ wtf.form_bool_field(form.disable_file_viewer) }} +

Whether files services via USB are enabled.

+
+
+ {{ wtf.form_bool_field(form.disable_app_store) }} +

Whether app store services are enabled.

+
+
+ {{ wtf.form_input_field(form.admin_password) }} +

Password for accessing this interface.

+
+
+ +
{{ form.butterbox_logo(class='label', style="width: 280px") }}
+ {% for error in form.butterbox_logo.errors %} +

{{ error }}

+ {% endfor %} +

This is the logo shown in the UI. Current logo:
+

+
+

{{ form.submit( class="button is-link") }}

+
+ + Logout + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..c869fc7 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,36 @@ + + + + + + {% if title %} + {{ title }} + {% else %} + "{{ get_setting('butterbox_name') }}" + {% endif %} + + + + + +
+
+ +

{{ get_setting('butterbox_name') }}

+
+ +
{% block content %}{% endblock %}
+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+
+ {% endif %} + {% endwith %} +
+ + diff --git a/app/templates/bulma_wtf.html b/app/templates/bulma_wtf.html new file mode 100644 index 0000000..42aab63 --- /dev/null +++ b/app/templates/bulma_wtf.html @@ -0,0 +1,20 @@ +{% macro form_input_field(field) %} +
+ {{ field.label(class='label')}} + {{ field(class='input' + (' is-danger' if field.errors else ' is_success'), type="text") }} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} +
+{% endmacro %} + + +{% macro form_bool_field(field) %} +
+ {{ field.label(class='label')}} + {{ field(class='checkbox', type="checkbox") }} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} +
+{% endmacro %} \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..c258b68 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +

Hi, welcome to the {{get_setting('butterbox_name')}}.

+

View and download the information you want from this offline box.

+
+ {% for service in services %} + +
{{ service.name }}
+ +
+
+ {% endfor %} + + +
+{% endblock %} \ No newline at end of file diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100644 index 0000000..46b7146 --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block content %} +{% import "bulma_wtf.html" as wtf %} + +

Sign In

+
+ {{ form.hidden_tag() }} +
{{ wtf.form_input_field(form.username) }}
+
{{ wtf.form_input_field(form.password) }}
+ +
{{ form.submit(class="button is-link") }}
+
+{% endblock %} diff --git a/app/translation_refs.py b/app/translation_refs.py new file mode 100644 index 0000000..398006e --- /dev/null +++ b/app/translation_refs.py @@ -0,0 +1,6 @@ +from flask_babel import gettext as _ + +_('ssid_description') +_('butterbox_name_description') +_('wifi_password_description') +_('logo_description') \ No newline at end of file diff --git a/app/translations/en/LC_MESSAGES/messages.po b/app/translations/en/LC_MESSAGES/messages.po new file mode 100644 index 0000000..d1fb43c --- /dev/null +++ b/app/translations/en/LC_MESSAGES/messages.po @@ -0,0 +1,88 @@ +# English translations for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-02-10 15:23+0000\n" +"PO-Revision-Date: 2026-02-10 15:29+0000\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.18.0\n" + +#: app/__init__.py:19 +msgid "Please log in to access this page." +msgstr "Please log in to access this page." + +#: app/forms.py:6 +msgid "Username" +msgstr "Username" + +#: app/forms.py:7 +msgid "Password" +msgstr "Password" + +#: app/forms.py:8 +msgid "Sign In" +msgstr "Sign In" + +#: app/forms.py:14 +msgid "WiFi Password" +msgstr "WiFi Password" + +#: app/forms.py:15 +msgid "Butterbox Name" +msgstr "Butterbox Name" + +#: app/forms.py:16 +msgid "Butterbox Logo" +msgstr "Butterbox Logo" + +#: app/forms.py:18 +msgid "Submit" +msgstr "Submit" + +#: app/routes.py:39 +msgid "Invalid username or password" +msgstr "Invalid username or password" + +#: app/translation_refs.py:3 +msgid "ssid_description" +msgstr "This is the name of the advertised Wi-Fi network." + +#: app/translation_refs.py:4 +msgid "butterbox_name_description" +msgstr "This is the secret key needed to connect to the Wi-Fi network. By default, this is not set and everyone can join." + +#: app/translation_refs.py:5 +msgid "wifi_password_description" +msgstr "This is the name shown in the UI, and used to access the box locally by adding .local or .lan in your browser." + +#: app/translation_refs.py:6 +msgid "logo_description" +msgstr "An image that will be used as the logo." + +#: app/templates/admin.html:4 +msgid "Application Settings" +msgstr "Application Settings" + +#: app/templates/base.html:15 +msgid "Home" +msgstr "Home" + +#: app/templates/base.html:32 +msgid "" +"Admin settings have changed! Restart the butterbox for changes to take " +"effect." +msgstr "" +"Admin settings have changed! Restart the butterbox for changes to take " +"effect." + diff --git a/app/translations/ro/LC_MESSAGES/messages.po b/app/translations/ro/LC_MESSAGES/messages.po new file mode 100644 index 0000000..a4e0abd --- /dev/null +++ b/app/translations/ro/LC_MESSAGES/messages.po @@ -0,0 +1,87 @@ +# Romanian translations for PROJECT. +# Copyright (C) 2026 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2026-02-10 15:23+0000\n" +"PO-Revision-Date: 2026-02-10 16:28+0000\n" +"Last-Translator: FULL NAME \n" +"Language: ro\n" +"Language-Team: ro \n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100" +" < 20)) ? 1 : 2);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.18.0\n" + +#: app/__init__.py:19 +msgid "Please log in to access this page." +msgstr "" + +#: app/forms.py:6 +msgid "Username" +msgstr "" + +#: app/forms.py:7 +msgid "Password" +msgstr "Password" + +#: app/forms.py:8 +msgid "Sign In" +msgstr "Sign In" + +#: app/forms.py:14 +msgid "WiFi Password" +msgstr "WiFi Password" + +#: app/forms.py:15 +msgid "Butterbox Name" +msgstr "Butterbox Name" + +#: app/forms.py:16 +msgid "Butterbox Logo" +msgstr "Butterbox Logo" + +#: app/forms.py:18 +msgid "Submit" +msgstr "Submit" + +#: app/routes.py:39 +msgid "Invalid username or password" +msgstr "" + +#: app/translation_refs.py:3 +msgid "ssid_description" +msgstr "" + +#: app/translation_refs.py:4 +msgid "butterbox_name_description" +msgstr "" + +#: app/translation_refs.py:5 +msgid "wifi_password_description" +msgstr "" + +#: app/translation_refs.py:6 +msgid "logo_description" +msgstr "" + +#: app/templates/admin.html:4 +msgid "Application Settings" +msgstr "" + +#: app/templates/base.html:15 +msgid "Home" +msgstr "" + +#: app/templates/base.html:32 +msgid "" +"Admin settings have changed! Restart the butterbox for changes to take " +"effect." +msgstr "" + diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..b42c3d5 --- /dev/null +++ b/babel.cfg @@ -0,0 +1,2 @@ +[python: app/**.py] +[jinja2: app/templates/**.html] diff --git a/butter-portal.py b/butter-portal.py new file mode 100644 index 0000000..d099b92 --- /dev/null +++ b/butter-portal.py @@ -0,0 +1 @@ +from app import app diff --git a/config.py b/config.py new file mode 100644 index 0000000..1b77d7e --- /dev/null +++ b/config.py @@ -0,0 +1,22 @@ +import os +basedir = os.path.abspath(os.path.dirname(__file__)) + +class Config: + SECRET_KEY = os.environ.get('SECRET_KEY') or 'msdskdoopancjsduinabemseruulmf781' + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ + 'sqlite:///' + os.path.join(basedir, 'app.db') + SETTINGS_CHANGED = False + BUTTERBOX_SSID= "butterbox" + BUTTERBOX_WIFI_PASSWORD= "" + LANGUAGES = ['en', 'ro'] + + BUTTERBOX_NAME = "Butter Box" + BUTTERBOX_LOGO = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjc5IiBoZWlnaHQ9IjIzNiIgdmlld0JveD0iMCAwIDI3OSAyMzYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIG9wYWNpdHk9IjAuOSI+CjxwYXRoIG9wYWNpdHk9IjAuOSIgZD0iTTEzOS4zODggMTk3LjYzNkMxNzIuMjg0IDE5Ny42MzYgMTk4Ljk1MiAxNzAuOTY4IDE5OC45NTIgMTM4LjA3MkMxOTguOTUyIDEwNS4xNzUgMTcyLjI4NCA3OC41MDcgMTM5LjM4OCA3OC41MDdDMTA2LjQ5MSA3OC41MDcgNzkuODIzIDEwNS4xNzUgNzkuODIzIDEzOC4wNzJDNzkuODIzIDE3MC45NjggMTA2LjQ5MSAxOTcuNjM2IDEzOS4zODggMTk3LjYzNloiIGZpbGw9IiNGRUY1QjgiIHN0cm9rZT0iI0ZFRjVCOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggb3BhY2l0eT0iMC45IiBkPSJNMTM5LjYxMiA0LjAzMjU5VjYwLjM3NTRDMTM0LjgxNyA1Ny40NTM0IDEzMS4xNDYgNDUuOTE1MSAxMzEuMTQ2IDMyLjIwNEMxMzEuMjIxIDE4LjQ5MjkgMTM0LjgxNyA2Ljk1NDYzIDEzOS42MTIgNC4wMzI1OVoiIGZpbGw9IiNGRUY1QjgiIHN0cm9rZT0iI0ZFRjVCOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggb3BhY2l0eT0iMC45IiBkPSJNNjAuNDE3OCAxMzkuNDJINEM2LjkyMjA0IDE0NC4yMTUgMTguNDYwMyAxNDcuODg3IDMyLjE3MTQgMTQ3Ljg4N0M0NS45NTc0IDE0Ny44ODcgNTcuNDk1NyAxNDQuMjE1IDYwLjQxNzggMTM5LjQyWiIgZmlsbD0iI0ZFRjVCOCIgc3Ryb2tlPSIjRkVGNUI4IiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIvPgo8cGF0aCBvcGFjaXR5PSIwLjkiIGQ9Ik0yNzUgMTM5LjY0NUgyMTguNjU3QzIyMS41NzkgMTM0Ljg1IDIzMy4xMTggMTMxLjE3OSAyNDYuODI5IDEzMS4xNzlDMjYwLjU0IDEzMS4yNTQgMjcyLjA3OCAxMzQuODUgMjc1IDEzOS42NDVaIiBmaWxsPSIjRkVGNUI4IiBzdHJva2U9IiNGRUY1QjgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIG9wYWNpdHk9IjAuOSIgZD0iTTIzNS4zNjUgNDMuODE3M0wxOTUuNTA2IDgzLjY3NjlDMTk0LjE1NyA3OC4yMDc0IDE5OS43NzYgNjcuNDkzMyAyMDkuNDQyIDU3LjgyODFDMjE5LjI1NyA0OC4wODggMjI5Ljg5NiA0Mi40Njg3IDIzNS4zNjUgNDMuODE3M1oiIGZpbGw9IiNGRUY1QjgiIHN0cm9rZT0iI0ZFRjVCOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggb3BhY2l0eT0iMC45IiBkPSJNODMuNjQ0MiA4My41MjY5TDQzLjc4NDcgNDMuNjY3NEM0Mi40MzYxIDQ5LjEzNjggNDguMDU1NCA1OS44NTA5IDU3LjcyMDUgNjkuNTE2MUM2Ny40NjA3IDc5LjI1NjIgNzguMTc0OCA4NC44NzU1IDgzLjY0NDIgODMuNTI2OVoiIGZpbGw9IiNGRUY1QjgiIHN0cm9rZT0iI0ZFRjVCOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggb3BhY2l0eT0iMC45IiBkPSJNMTgyLjkxOCAzNC45MDEyTDE2Ny41NTkgNzIuMTM4NUMxNjUuMTYxIDY4LjkxNjcgMTY1LjkxMSA2MC4zMDA1IDE2OS42NTcgNTEuMjM0N0MxNzMuNDAzIDQyLjE2ODkgMTc4Ljk0NyAzNS41MDA2IDE4Mi45MTggMzQuOTAxMloiIGZpbGw9IiNGRUY1QjgiIHN0cm9rZT0iI0ZFRjVCOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggb3BhY2l0eT0iMC45IiBkPSJNNzMuMDA1MSAxMTQuMDIxTDM1Ljg0MjggOTguNjYxNkMzNi40NDIyIDEwMi42MzMgNDMuMTEwNCAxMDguMTc3IDUyLjE3NjIgMTExLjkyM0M2MS4yNDIgMTE1LjY2OSA2OS43ODMzIDExNi40MTkgNzMuMDA1MSAxMTQuMDIxWiIgZmlsbD0iI0ZFRjVCOCIgc3Ryb2tlPSIjRkVGNUI4IiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIvPgo8cGF0aCBvcGFjaXR5PSIwLjkiIGQ9Ik0yNDcuNjUzIDE4NC41MjRMMjEwLjQxNiAxNjkuMjRDMjEzLjYzNyAxNjYuODQyIDIyMi4yNTQgMTY3LjU5MiAyMzEuMzE5IDE3MS4zMzhDMjQwLjM4NSAxNzUuMDg0IDI0Ny4wNTMgMTgwLjU1MyAyNDcuNjUzIDE4NC41MjRaIiBmaWxsPSIjRkVGNUI4IiBzdHJva2U9IiNGRUY1QjgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIG9wYWNpdHk9IjAuOSIgZD0iTTY3LjIzNTkgMTcwLjg4OEwzMC4wNzM2IDE4Ni4zMjNDMzMuMjk1MyAxODguNzIgNDEuODM2NyAxODcuOTcxIDUwLjkwMjUgMTg0LjIyNUM2MC4wNDMyIDE4MC40MDQgNjYuNjM2NSAxNzQuODU5IDY3LjIzNTkgMTcwLjg4OFoiIGZpbGw9IiNGRUY1QjgiIHN0cm9rZT0iI0ZFRjVCOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggb3BhY2l0eT0iMC45IiBkPSJNMjQ1LjEwNSA5NS4yODk5TDIwNy45NDMgMTEwLjc5OUMyMDguNTQyIDEwNi44MjggMjE1LjEzNiAxMDEuMjg0IDIyNC4yMDIgOTcuNTM3NkMyMzMuMjY3IDkzLjcxNjUgMjQxLjgwOSA5Mi44OTI0IDI0NS4xMDUgOTUuMjg5OVoiIGZpbGw9IiNGRUY1QjgiIHN0cm9rZT0iI0ZFRjVCOCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggb3BhY2l0eT0iMC45IiBkPSJNMTEwLjA5MiA3My4wMzc3TDk0LjY1OCAzNS44NzU0QzkyLjI2MDQgMzkuMDk3MSA5My4wODQ2IDQ3LjcxMzQgOTYuODMwOCA1Ni43NzkyQzEwMC41NzcgNjUuODQ1IDEwNi4xMjEgNzIuNDM4MyAxMTAuMDkyIDczLjAzNzdaIiBmaWxsPSIjRkVGNUI4IiBzdHJva2U9IiNGRUY1QjgiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjwvZz4KPHBhdGggZD0iTTIwMy4xMyAxNzguOTM2QzE5OC4xMTkgMTc4LjIyIDE5Mi4zOTMgMTc3Ljc5MSAxOTAuMjQ2IDE3NS41NzJDMTg4LjQ1NyAxNzMuNzExIDE5Mi44OTQgMTY3LjYyNyAxODguNzQzIDE2MS44M0MxODcuODg0IDE2MC42ODUgMTg2LjUyNCAxNTkuNTM5IDE4NC45NSAxNTguNjA5VjEzOS43MTNDMTg0Ljk1IDEzNi4xMzUgMTgzLjE2IDEzMi44NDIgMTgwLjE1NCAxMzAuODM4TDE0NC40MzkgMTA4LjA3OEMxNDAuOTMyIDEwNS43ODcgMTM2LjQyMyAxMDUuNzg3IDEzMi45MTUgMTA4LjA3OEw5Ny4xMjg1IDEzMC45MUM5NC4xMjI0IDEzMi44NDIgOTIuMzMzIDEzNi4yMDYgOTIuMzMzIDEzOS43MTNWMTU4Ljc1MkM5MS4yNTk0IDE1OS4zOTYgOTAuMzI5IDE1OS45NjkgODkuNzU2NCAxNjAuMzk4QzgzLjk1ODkgMTY0LjMzNSA4OC4wMzg2IDE3MS40OTIgODUuODE5OCAxNzUuMjg2QzgzLjAyODQgMTc5Ljg2NiA4MC40NTE3IDE3OC44NjQgNzMuMDA4IDE4MC45NEM2Ni40OTQ4IDE4Mi42NTggNjMuNDE3MSAxOTQuMTgxIDg2LjUzNTUgMTg4LjQ1NUM4OS4xMTIyIDE4Ny44MTEgOTAuNTQzNiAxODcuNTI1IDkzLjQ3ODIgMTg5LjQ1N0MxMDAuNTY0IDE5My45NjYgOTMuMzM1MSAyMDguMjEgOTkuOTkxNCAyMTAuNDI4QzEwOS44NjkgMjEzLjcyMSAxMDIuNDI1IDE4OS40NTcgMTA4LjE1MSAxOTEuMDMyQzExMC44NzEgMTkyLjM5MiAxMTMuMzA0IDE5NC4zOTYgMTE0LjMwNiAxOTcuNjE3QzExNy4xNjkgMjA1LjIwNCAxMTIuNzMyIDIzMy4yNjEgMTIwLjAzMiAyMzQuNDc3QzEyOC40NzggMjM1Ljc2NiAxMjQuMTEyIDIwMy43IDEyOC4wNDggMjAyLjI2OUMxMzAuNDEgMjAxLjQxIDEzMi43MDEgMjA1LjU2MSAxMzguNDk4IDIwNi4yMDZDMTQzLjE1MSAyMDYuNzA3IDE0Ny45NDYgMjAzLjcgMTQ5LjY2NCAyMDIuMjY5QzE1NC41MzEgMTk4LjExOCAxNTcuODk1IDE5Ni41NDMgMTYxLjgzMSAxOTYuMTE0QzE3Mi4zNTMgMTk0Ljg5NyAxNjMuMTkxIDIyMC45NSAxNzIuOTk3IDIxOS44MDVDMTgxLjY1NyAyMTYuOTQyIDE3MS4yMDggMTk1LjA0IDE4Ni44MTEgMTg5LjI0M0MxOTIuODk0IDE4Ny4wOTUgMjA1LjA2MiAxOTAuMDMgMjA5LjI4NSAxODguMzEyQzIxMy4zNjUgMTg2LjUyMyAyMTEuNjQ3IDE3OS43OTUgMjAzLjEzIDE3OC45MzZaIiBmaWxsPSIjRkNFMzYzIi8+CjxwYXRoIGQ9Ik0xMjAuNTMzIDIzNS45MDlDMTIwLjMxOCAyMzUuOTA5IDEyMC4wMzIgMjM1LjkwOSAxMTkuODE3IDIzNS44MzdDMTE0LjE2MyAyMzQuOTA3IDExNC4wOTIgMjI0LjA5OSAxMTQuMDIgMjEyLjY0N0MxMTQuMDIgMjA2Ljc3OCAxMTMuOTQ4IDIwMC42OTQgMTEyLjk0NiAxOTguMDQ2VjE5Ny45NzVDMTEyLjIzMSAxOTUuNjg0IDExMC41MTMgMTkzLjgyMyAxMDcuNzIxIDE5Mi4zMkMxMDcuMTQ5IDE5My4xMDggMTA3LjA3NyAxOTYuODI5IDEwNy4wNzcgMTk5LjEyQzEwNy4wNzcgMjA0LjEzIDEwNy4wMDYgMjA5LjM1NSAxMDQuMjE0IDIxMS4zNTlDMTAyLjk5OCAyMTIuMjE4IDEwMS40MjMgMjEyLjM2MSA5OS41NjIgMjExLjc4OEM5NS4yNjc2IDIxMC4zNTcgOTUuMzM5MiAyMDUuNTYxIDk1LjQ4MjMgMjAwLjk4MUM5NS41NTM5IDE5Ni44MjkgOTUuNjI1NCAxOTIuNTM1IDkyLjc2MjUgMTkwLjY3NEM5MC4yNTc0IDE4OS4wMjggODkuMjU1NCAxODkuMjQzIDg2Ljk2NSAxODkuODg3QzcwLjcxNzcgMTkzLjg5NSA2Ny42NCAxODkuMzg2IDY3LjEzOSAxODcuMjM5QzY2LjM1MTcgMTg0LjE2MSA2OC45OTk5IDE4MC41ODIgNzIuNzkzMyAxNzkuNThDNzQuODY5IDE3OS4wMDggNzYuNTg2OCAxNzguNzIxIDc3Ljk0NjcgMTc4LjQzNUM4MS42Njg1IDE3Ny43MTkgODMuMDI4NCAxNzcuNDMzIDg0Ljc0NjIgMTc0LjY0MUM4NS4zOTA0IDE3My41NjggODUuMzE4OCAxNzEuNzc5IDg1LjI0NzIgMTY5LjkxOEM4NS4xMDQgMTY2LjQ4MiA4NC44ODkzIDE2Mi4xODggODkuMTEyMiAxNTkuMzI1Qzg5LjU0MTYgMTU4Ljk2NyA5MC4xODU4IDE1OC41MzcgOTEuMDQ0NyAxNTguMDM2VjEzOS43ODVDOTEuMDQ0NyAxMzUuNzc3IDkzLjEyMDMgMTMxLjkxMiA5Ni40ODQzIDEyOS43NjVMMTMyLjI3MSAxMDYuOTMyQzEzNi4yMDggMTA0LjM1NiAxNDEuMzYxIDEwNC4zNTYgMTQ1LjM2OSAxMDYuOTMyTDE4MS4wODUgMTI5LjY5M0MxODQuNTIgMTMxLjk4MyAxODYuNTI0IDEzNS43MDUgMTg2LjUyNCAxMzkuNzg1VjE1Ny44OTNDMTg3Ljk1NiAxNTguODI0IDE4OS4xNzMgMTU5Ljk2OSAxOTAuMDMyIDE2MS4wNDJDMTkyLjk2NiAxNjUuMTIyIDE5Mi4xNzkgMTY5LjI3MyAxOTEuNjc4IDE3Mi4wNjVDMTkxLjUzNSAxNzIuOTk1IDE5MS4yNDggMTc0LjM1NSAxOTEuNDYzIDE3NC42NDFDMTkyLjk2NiAxNzYuMjE2IDE5Ny40MDQgMTc2Ljc4OSAyMDEuMzQgMTc3LjI5QzIwMi4wNTYgMTc3LjM2MSAyMDIuNzcyIDE3Ny41MDQgMjAzLjQxNiAxNzcuNTc2QzIwOS43ODYgMTc4LjI5MiAyMTIuNTA2IDE4MS43MjcgMjEyLjkzNSAxODQuNDQ3QzIxMy4yOTMgMTg2LjY2NiAyMTIuMTQ4IDE4OC43NDIgMjEwLjAwMSAxODkuNjcyQzIwNy43MSAxOTAuNjAyIDIwMy45MTcgMTkwLjM4OCAxOTkuNjIyIDE5MC4xMDFDMTk1LjMyOCAxODkuODg3IDE5MC4zODkgMTg5LjYgMTg3LjQ1NSAxOTAuNjAyQzE3OS40MzkgMTkzLjYwOSAxNzkuMDgxIDIwMS4xOTUgMTc4Ljc5NCAyMDguNTY4QzE3OC41MDggMjE0LjI5MyAxNzguMjkzIDIxOS42NjIgMTczLjY0MSAyMjEuMjM2TDE3My4zNTUgMjIxLjMwOEMxNzEuNzggMjIxLjUyMiAxNzAuNDIgMjIxLjE2NSAxNjkuMzQ3IDIyMC4yMzRDMTY2Ljc3IDIxOC4wODcgMTY2LjYyNyAyMTMuMzYzIDE2Ni40MTIgMjA4LjQyNEMxNjYuMjY5IDIwNC4zNDUgMTY2LjA1NCAxOTkuNjkyIDE2NC4zMzYgMTk4LjE4OUMxNjMuOTc5IDE5Ny45MDMgMTYzLjQwNiAxOTcuNTQ1IDE2Mi4xODkgMTk3LjY4OEMxNTguMzk2IDE5OC4xMTggMTU1LjI0NyAxOTkuNjkyIDE1MC44MDkgMjAzLjQ4NkMxNTAuNTIzIDIwMy43MDEgMTQ0Ljc5NyAyMDguNDI0IDEzOC41NyAyMDcuNzhDMTM1LjEzNCAyMDcuNDIyIDEzMi44NDQgMjA1LjkxOSAxMzEuMTI2IDIwNC44NDZDMTMwLjEyNCAyMDQuMjAyIDEyOS4yNjUgMjAzLjcwMSAxMjguODM2IDIwMy43NzJDMTI4LjA0OCAyMDQuODQ2IDEyNy43NjIgMjEwLjcxNSAxMjcuNjE5IDIxNS4wMDlDMTI3LjI2MSAyMjMuNTI3IDEyNi45MDMgMjMyLjMzIDEyMy4zOTYgMjM1LjE5M0MxMjIuMzk0IDIzNS41NTEgMTIxLjQ2NCAyMzUuOTA5IDEyMC41MzMgMjM1LjkwOVpNMTE1LjY2NiAxOTcuMTE2QzExNi44MTEgMjAwLjI2NSAxMTYuODgzIDIwNi4yNzcgMTE2Ljg4MyAyMTIuNjQ3QzExNi44ODMgMjIwLjUyIDExNi45NTQgMjMyLjQ3MyAxMjAuMjQ3IDIzMy4wNDZDMTIwLjY3NiAyMzMuMTE3IDEyMS4wMzQgMjMzLjA0NiAxMjEuMzIxIDIzMi43NkMxMjMuODI2IDIzMC43NTYgMTI0LjE4MyAyMjEuNDUxIDEyNC40NyAyMTQuNjUxQzEyNC44MjggMjA1Ljk5MSAxMjUuMTE0IDIwMS43NjggMTI3LjQ3NiAyMDAuOTA5QzEyOS4yNjUgMjAwLjI2NSAxMzAuNzY4IDIwMS4yNjcgMTMyLjQxNCAyMDIuMjY5QzEzNC4wNjEgMjAzLjI3MSAxMzUuODUgMjA0LjQ4OCAxMzguNTcgMjA0Ljc3NEMxNDIuNzkzIDIwNS4yMDQgMTQ3LjMwMiAyMDIuMzQxIDE0OC42NjIgMjAxLjE5NUMxNTMuNTI5IDE5Ny4wNDQgMTU3LjE3OSAxOTUuMTgzIDE2MS42MTcgMTk0LjY4MkMxNjMuMzM0IDE5NC40NjcgMTY0LjgzOCAxOTQuODk3IDE2NS45ODMgMTk1LjgyN0MxNjguNzAzIDE5OC4xMTggMTY4LjkxNyAyMDIuOTg1IDE2OS4wNiAyMDguMTM4QzE2OS4yMDQgMjEyLjA3NSAxNjkuNDE4IDIxNi41ODQgMTcwLjk5MyAyMTcuOTQ0QzE3MS4yNzkgMjE4LjE1OCAxNzEuNzA5IDIxOC40NDUgMTcyLjYzOSAyMTguMzczQzE3NS4xNDQgMjE3LjQ0MyAxNzUuNDMgMjEzLjkzNiAxNzUuNjQ1IDIwOC4zNTNDMTc2LjAwMyAyMDAuODM4IDE3Ni4zNjEgMTkxLjUzMyAxODYuMjM4IDE4Ny44ODNDMTg5Ljc0NSAxODYuNjY2IDE5NC43NTUgMTg2Ljk1MiAxOTkuNTUxIDE4Ny4yMzlDMjAzLjIwMSAxODcuNDUzIDIwNi45OTUgMTg3LjY2OCAyMDguNjQxIDE4Ny4wMjRDMjA5LjU3MSAxODYuNjY2IDIxMC4wMDEgMTg1Ljg3OSAyMDkuODU4IDE4NC44NzdDMjA5LjU3MSAxODMuMDg3IDIwNy4yODEgMTgwLjg2OCAyMDIuODQzIDE4MC40MzlIMjAyLjc3MkMyMDIuMDU2IDE4MC4zNjcgMjAxLjQxMiAxODAuMjI0IDIwMC42OTYgMTgwLjE1M0MxOTUuODI5IDE3OS41MDkgMTkxLjMyIDE3OC45MzYgMTg5LjEwMSAxNzYuNjQ2QzE4Ny44ODQgMTc1LjM1NyAxODguMjQyIDE3My40OTYgMTg4LjYgMTcxLjU2NEMxODkuMTAxIDE2OC45ODcgMTg5LjY3NCAxNjUuNzY2IDE4Ny40NTUgMTYyLjc2QzE4Ni43MzkgMTYxLjgzIDE4NS41MjIgMTYwLjc1NiAxODQuMDkxIDE1OS45NjlMMTgzLjM3NSAxNTkuNTM5VjEzOS44NTdDMTgzLjM3NSAxMzYuNzc5IDE4MS44MDEgMTMzLjkxNiAxNzkuMjI0IDEzMi4xOThMMTQzLjY1MiAxMDkuMjIzQzE0MC42NDUgMTA3LjIxOSAxMzYuNzA5IDEwNy4yMTkgMTMzLjcwMyAxMDkuMjIzTDk3LjkxNTggMTMyLjA1NUM5NS4zMzkxIDEzMy43MDEgOTMuNzY0NSAxMzYuNjM2IDkzLjc2NDUgMTM5LjY0MlYxNTkuNDY4TDkzLjA0ODggMTU5Ljg5N0M5Mi4xMTgzIDE2MC40NyA5MS4xMTYzIDE2MS4wNDIgOTAuNjE1MiAxNjEuNDcyQzg3LjY4MDcgMTYzLjQ3NiA4Ny44MjM5IDE2Ni40ODIgODcuOTY3IDE2OS42MzFDODguMDM4NiAxNzEuODUgODguMTgxOCAxNzQuMTQgODcuMTA4MSAxNzYuMDAxQzg0Ljc0NjIgMTc5LjkzOCA4Mi4yNDExIDE4MC40MzkgNzguNDQ3NyAxODEuMTU1Qzc3LjAxNjIgMTgxLjQ0MSA3NS40NDE2IDE4MS43MjcgNzMuNTA5MSAxODIuM0M3MS4wNzU2IDE4Mi45NDQgNjkuNTcyNSAxODUuMDkxIDY5LjkzMDQgMTg2LjQ1MUM3MC4zNTk4IDE4OC4wOTcgNzQuMjk2NCAxOTAuMDMgODYuMzIwOCAxODcuMDk1Qzg5LjExMjIgMTg2LjM4IDkxLjA0NDcgMTg2LjA5MyA5NC40MDg3IDE4OC4zMTJDOTguNzAzMSAxOTEuMDMyIDk4LjU2IDE5Ni40IDk4LjQ4ODQgMjAxLjEyNEM5OC40MTY4IDIwNS42MzMgOTguNDg4NCAyMDguNDk2IDEwMC42MzYgMjA5LjIxMkMxMDIuMDY3IDIwOS43MTMgMTAyLjU2OCAyMDkuMzU1IDEwMi43MTEgMjA5LjIxMkMxMDQuMzU3IDIwOC4wNjcgMTA0LjM1NyAyMDIuOTg1IDEwNC4zNTcgMTk5LjMzNUMxMDQuMzU3IDE5NC45NjggMTA0LjQyOSAxOTEuNjA1IDEwNi4yMTggMTkwLjI0NUMxMDYuNzE5IDE4OS44ODcgMTA3LjUwNyAxODkuNTI5IDEwOC43MjMgMTg5LjgxNUwxMDkuMDEgMTg5Ljg4N0MxMTIuNDQ1IDE5MS41MzMgMTE0LjczNiAxOTMuOTY2IDExNS42NjYgMTk3LjExNloiIGZpbGw9IiMwMTAwMDQiLz4KPHBhdGggZD0iTTE1MC4wMjIgMTY2LjEyNEMxNTIuODEzIDE3MC4zNDcgMTQ2LjA4NSAxOTQuMTEgMTM4LjI4NCAxOTQuMTFDMTI4LjY5MyAxOTQuMTEgMTIzLjg5NyAxNjkuODQ2IDEyNi40NzQgMTY2LjEyNEMxMjcuOTA1IDE2NC4wNDkgMTMxLjQxMyAxNjguMzQzIDEzOC42NDEgMTY4LjM0M0MxNDUuMzY5IDE2OC4zNDMgMTQ4LjU5IDE2My45MDUgMTUwLjAyMiAxNjYuMTI0WiIgZmlsbD0iIzAxMDAwNCIvPgo8cGF0aCBkPSJNMTc1LjIxNiAxNTkuNjgzQzE3Ni45MzQgMTYwLjExMiAxNzguMjkzIDE1OC4zMjMgMTc3LjA3NyAxNTcuMTc4QzE3NS4yODcgMTU1LjYwMyAxNzIuMTM4IDE1My4xNjkgMTY2LjU1NSAxNTMuMTY5QzE2MS4xMTYgMTUzLjE2OSAxNTcuNzUyIDE1NS42MDMgMTU1LjY3NiAxNTcuMjQ5QzE1NC4yNDUgMTU4LjM5NCAxNTUuNjc2IDE2MC4zMjcgMTU3LjUzNyAxNTkuODI2QzE2MC4wNDIgMTU5LjE4MiAxNjMuMjYzIDE1Ny42MDcgMTY2LjY5OCAxNTcuNjA3QzE3MC4xMzQgMTU3Ljc1IDE3My4wNjkgMTU5LjE4MiAxNzUuMjE2IDE1OS42ODNaIiBmaWxsPSIjMDEwMDA0Ii8+CjxwYXRoIGQ9Ik0xMTkuMTAyIDE1OS42ODNDMTIwLjgxOSAxNjAuMTEyIDEyMi4xNzkgMTU4LjMyMyAxMjAuOTYzIDE1Ny4xNzhDMTE5LjE3MyAxNTUuNjAzIDExNi4wMjQgMTUzLjE2OSAxMTAuNDQxIDE1My4xNjlDMTA1LjAwMiAxNTMuMTY5IDEwMS42MzggMTU1LjYwMyA5OS41NjIgMTU3LjI0OUM5OC4xMzA1IDE1OC4zOTQgOTkuNTYyIDE2MC4zMjcgMTAxLjQyMyAxNTkuODI2QzEwMy45MjggMTU5LjE4MiAxMDcuMTQ5IDE1Ny42MDcgMTEwLjU4NCAxNTcuNjA3QzExNC4wOTIgMTU3Ljc1IDExNi45NTUgMTU5LjE4MiAxMTkuMTAyIDE1OS42ODNaIiBmaWxsPSIjMDEwMDA0Ii8+CjxwYXRoIGQ9Ik0xMDkuODY5IDE4Mi41ODZDMTE2LjE1NCAxODIuNTg2IDEyMS4yNDkgMTc4LjUxNyAxMjEuMjQ5IDE3My40OTZDMTIxLjI0OSAxNjguNDc2IDExNi4xNTQgMTY0LjQwNiAxMDkuODY5IDE2NC40MDZDMTAzLjU4MyAxNjQuNDA2IDk4LjQ4ODMgMTY4LjQ3NiA5OC40ODgzIDE3My40OTZDOTguNDg4MyAxNzguNTE3IDEwMy41ODMgMTgyLjU4NiAxMDkuODY5IDE4Mi41ODZaIiBmaWxsPSIjRkVGNUI4Ii8+CjxwYXRoIGQ9Ik0xNjYuNjk4IDE4Mi41ODZDMTcyLjk4MyAxODIuNTg2IDE3OC4wNzkgMTc4LjUxNyAxNzguMDc5IDE3My40OTZDMTc4LjA3OSAxNjguNDc2IDE3Mi45ODMgMTY0LjQwNiAxNjYuNjk4IDE2NC40MDZDMTYwLjQxMyAxNjQuNDA2IDE1NS4zMTggMTY4LjQ3NiAxNTUuMzE4IDE3My40OTZDMTU1LjMxOCAxNzguNTE3IDE2MC40MTMgMTgyLjU4NiAxNjYuNjk4IDE4Mi41ODZaIiBmaWxsPSIjRkVGNUI4Ii8+CjxwYXRoIGQ9Ik0xMzMuMjAyIDE1Mi4wOTZMMTA3LjQzNSAxMzYuMjA2QzEwNS45MzIgMTM1LjI3NiAxMDUuOTMyIDEzMy4wNTcgMTA3LjQzNSAxMzIuMTI3TDEzMy4yMDIgMTE2LjIzN0MxMzYuMzUxIDExNC4zMDUgMTQwLjI4OCAxMTQuMzA1IDE0My40MzcgMTE2LjIzN0wxNjkuMjAzIDEzMi4xMjdDMTcwLjcwNiAxMzMuMDU3IDE3MC43MDYgMTM1LjI3NiAxNjkuMjAzIDEzNi4yMDZMMTQzLjQzNyAxNTIuMDk2QzE0MC4yODggMTU0LjAyOCAxMzYuMjc5IDE1NC4wMjggMTMzLjIwMiAxNTIuMDk2WiIgZmlsbD0iI0ZFRjVCOCIvPgo8cGF0aCBkPSJNMTM1LjYzNSAxOTAuMzg4QzEzMi4yNzEgMTg3LjgxMSAxMzMuMjAyIDE3OC41NzggMTM5LjE0MiAxNzcuNTA0QzE0NC4xNTMgMTc2LjU3NCAxNDYuNTE1IDE3OC4yMiAxNDMuMzY1IDE4NC45NDhDMTQwLjIxNiAxOTEuODkxIDEzNy4yMSAxOTEuNTMzIDEzNS42MzUgMTkwLjM4OFoiIGZpbGw9IiNGRUY1QjgiLz4KPC9zdmc+Cg==" + ADMIN_PASSWORD = "admin" + + DISABLE_ACCESS_POINT = "false" + DISABLE_CHAT = "false" + DISABLE_APP_STORE = "false" + DISABLE_MAP_VIEWER = "false" + DISABLE_FILE_VIEWER = "false" + CONVENE_INSTALL_PATH = "/chat" \ No newline at end of file