diff --git a/app/commands.py b/app/commands.py index 73bd8ee..c9a2e67 100644 --- a/app/commands.py +++ b/app/commands.py @@ -15,9 +15,7 @@ def seed_defaults(): "onboarding_complete": "false", "lock_root_password": "false", "enable_file_viewer": current_app.config["ENABLE_FILE_VIEWER"], - "enable_map_viewer": current_app.config["ENABLE_MAP_VIEWER"], "enable_chat": current_app.config["ENABLE_CHAT"], - "enable_app_store": current_app.config["ENABLE_APP_STORE"], "enable_deltachat": current_app.config["ENABLE_DELTACHAT"], "ssh_password": "", "admin_password": current_app.config["ADMIN_PASSWORD"], @@ -26,6 +24,7 @@ def seed_defaults(): "root_account_settings": "", "ssh_access_settings": "disable_ssh", "root_password": "", + "first_setup": "true", } for key, value in defaults.items(): diff --git a/app/forms.py b/app/forms.py index 0b8dd12..ebb878e 100644 --- a/app/forms.py +++ b/app/forms.py @@ -10,13 +10,13 @@ def hostname_check(form, field): regex= re.compile("[a-zA-Z0-9-_]+") matches = re.fullmatch(regex, field.data) if not matches: - raise ValidationError(_l('Only dashes, underscores, letters and numbers allowed')) + raise ValidationError(_l('Only dashes, underscores, letters and numbers allowed.')) def wifi_length_check(form, field): if len(field.data) > 63: - raise ValidationError('Wifi password cannot be longer than 63 characters.') + raise ValidationError(_l('Wifi password cannot be longer than 63 characters.')) if len(field.data) in range(1,9): - raise ValidationError('Wifi password cannot be shorter than 8 characters.') + raise ValidationError(_l('Wifi password cannot be shorter than 8 characters.')) class LoginForm(FlaskForm): username = StringField(_l('Username'), validators=[DataRequired()]) @@ -24,6 +24,34 @@ class LoginForm(FlaskForm): submit = SubmitField(_l('Sign In')) remember_me = BooleanField('Remember Me') +class Step1Form(FlaskForm): + enable_file_viewer = BooleanField(_l('Enable File Viewer')) + enable_chat = BooleanField(_l('Enable Chat')) + enable_deltachat = BooleanField(_l('Enable DeltaChat')) + submit = SubmitField(_l('Next')) + +class Step2Form(FlaskForm): + butterbox_name = StringField(_l('Butterbox Name'), validators=[DataRequired()]) + butterbox_logo = FileField((_l('Butterbox Logo')), validators=[FileAllowed(['jpg', 'png', 'svg'], 'Images only!')]) + butterbox_hostname = StringField(_l('Butterbox Hostname'), validators=[DataRequired(), Length(1, 64), hostname_check]) + submit = SubmitField(_l('Next')) + +class Step3Form(FlaskForm): + ssid = StringField(_l('WiFi Name'), validators=[DataRequired(), Length(1, 64), hostname_check]) + wifi_password = StringField(_l('WiFi Password'), validators=[wifi_length_check]) + enable_wifi_sharing = BooleanField(_l('Enable WiFi Sharing')) + enable_access_point = BooleanField(_l('Enable Access Point')) + submit = SubmitField(_l('Next')) + +class Step4Form(FlaskForm): + admin_password = StringField(_l('Admin Password'), widget=PasswordInput(hide_value=False)) + root_password = StringField(_l('Root Password'), widget=PasswordInput(hide_value=False), validators=[DataRequired()]) + root_account_settings= RadioField(_l('Secure Root Account Method'), choices=[ ('lock_root_account', _l('Lock root account')), ( 'set_root_password', _l('Use root password'))], validators=[DataRequired()]) + ssh_access_settings = RadioField(_l('SSH Access Method'), choices=[ ('disable_ssh', _l('Disable SSH')), ( 'enable_ssh_with_root_password', _l('Enable SSH with root password')), ], validators=[DataRequired()]) + # ('enable_ssh_with_public_key', 'Enable SSH with public key'), + lock_root_account = BooleanField(_l('Lock Root Account')) + submit = SubmitField(_l('Apply Changes')) + class SettingsForm(FlaskForm): # Access point settings @@ -32,23 +60,19 @@ class SettingsForm(FlaskForm): enable_access_point = BooleanField(_l('Enable 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!')]) + butterbox_logo = FileField((_l('Butterbox Logo')), validators=[FileAllowed(['jpg', 'png', 'svg'], _l('Images only!'))]) # Services settings enable_file_viewer = BooleanField(_l('Enable File Viewer')) - enable_map_viewer = BooleanField(_l('Enable Map Viewer')) enable_chat = BooleanField(_l('Enable Chat')) - enable_app_store = BooleanField(_l('Enable App Store')) enable_deltachat = BooleanField(_l('Enable DeltaChat')) enable_wifi_sharing = BooleanField(_l('Enable WiFi Sharing')) # Access Settings - admin_password = StringField('Admin Password', widget=PasswordInput(hide_value=False)) - root_password = StringField('Root Password', widget=PasswordInput(hide_value=False), validators=[DataRequired()]) + admin_password = StringField(_l('Admin Password'), widget=PasswordInput(hide_value=False)) + root_password = StringField(_l('Root Password'), widget=PasswordInput(hide_value=False), validators=[DataRequired()]) - root_account_settings= RadioField(_l('Secure Root Account Method'), choices=[ ('lock_root_account', 'Lock root account'), ( 'set_root_password', 'Use root password')], validators=[DataRequired()]) - ssh_access_settings = RadioField(_l('SSH Access Method'), choices=[ ('disable_ssh', 'Disable SSH'), ( 'enable_ssh_with_root_password', 'Enable SSH with root password'), ], validators=[DataRequired()]) + root_account_settings= RadioField(_l('Secure Root Account Method'), choices=[('lock_root_account', _l('Lock root account')), ( 'set_root_password', _l('Use root password'))], validators=[DataRequired()]) + ssh_access_settings = RadioField(_l('SSH Access Method'), choices=[ ('disable_ssh', _l('Disable SSH')), ( 'enable_ssh_with_root_password', _l('Enable SSH with root password')), ], validators=[DataRequired()]) # ('enable_ssh_with_public_key', 'Enable SSH with public key'), lock_root_account = BooleanField(_l('Lock Root Account')) - butterbox_hostname = StringField(_l('Butterbox Hostname'), validators=[DataRequired(), Length(1, 64), hostname_check]) - submit = SubmitField(_l('Submit')) apply_changes = SubmitField(_l('Apply Changes')) diff --git a/app/routes.py b/app/routes.py index a94bfb6..a0358a0 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,6 +1,6 @@ from app import app from flask import render_template, flash, redirect, url_for, send_file -from app.forms import LoginForm, SettingsForm +from app.forms import LoginForm, SettingsForm, Step1Form, Step2Form, Step3Form, Step4Form from flask_login import login_user, current_user, logout_user, login_required import sqlalchemy as sa from app import db @@ -16,6 +16,7 @@ import string import glob import time import qrcode +from flask_babel import lazy_gettext as _l CHANGES_REQUIRING_RESTART = ['wifi_password', 'ssid', 'enable_access_point', 'enable_chat', 'enable_delta_chat', 'butterbox_hostname', 'ssh_access_settings', 'root_account_settings', 'root_password'] @@ -60,7 +61,6 @@ def get_files_in_path(path: str): "icon_url": get_file_icon_url(x), "relative_path": x.replace(app.config["BUTTERBOX_USB_PATH"], "")} for x in list_of_files] - print(file_list) return file_list def get_setting(name) -> str: @@ -82,8 +82,6 @@ def dump_settings(filename: str) -> None: @app.route('/index') def index(): enable_chat = get_setting("enable_chat") - enable_app_store = get_setting("enable_app_store") - enable_map_viewer = get_setting("enable_map_viewer") enable_file_viewer = get_setting("enable_file_viewer") enable_deltachat = get_setting("enable_deltachat") enable_wifi_sharing = get_setting("enable_wifi_sharing") @@ -94,19 +92,19 @@ def index(): 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 enable_wifi_sharing == 'true': - service_array.append({"name": "Share WiFi", "image": url_for("static", filename="images/share-icon.svg"), "url": url_for("share")}) + service_array.append({"name": _l("Share WiFi"), "image": url_for("static", filename="images/share-icon.svg"), "url": url_for("share")}) if enable_deltachat == 'true': - service_array.append({"name": "Secure Messaging", "image": url_for("static", filename="images/deltachat-icon.svg"), "url": url_for("messaging") }) + service_array.append({"name": _l("Secure Messenger"), "image": url_for("static", filename="images/deltachat-icon.svg"), "url": url_for("messaging") }) if enable_chat == 'true': - service_array.append({"name": "Message Board", "image": url_for("static", filename="images/chat-icon.png"), "url": f"{app.config["CONVENE_INSTALL_PATH"]}/#/room/join/%23public%3abutterbox.local"}) - if enable_app_store == 'true' and usb_has_appstore: - service_array.append({"name": "Apps", "image": url_for("static", filename="images/appstore-icon.svg")}) - if enable_map_viewer == 'true' and usb_has_maps: - service_array.append({"name": "Offline Maps", "image": url_for("static", filename="images/maps-icon.png")}) + service_array.append({"name": _l("Local Chat"), "image": url_for("static", filename="images/chat-icon.png"), "url": f"{app.config["CONVENE_INSTALL_PATH"]}/#/room/join/%23public%3abutterbox.local"}) + if enable_file_viewer == 'true' and usb_has_appstore: + service_array.append({"name": _l("Apps"), "image": url_for("static", filename="images/appstore-icon.svg")}) + if enable_file_viewer == 'true' and usb_has_maps: + service_array.append({"name": _l("Maps"), "image": url_for("static", filename="images/maps-icon.png")}) if enable_file_viewer == 'true': - name = "Files" + name = _l("Files") if not usb_inserted: - name = "Insert USB to browse files" + name = _l("Insert USB to browse files") service_array.append({ "name": name, "image": url_for("static", filename="images/explore-icon.svg"), @@ -150,26 +148,151 @@ def login(): return render_template('login.html', title='Sign in', form=form, get_setting=get_setting) +@app.route('/first_setup') +def first_setup(): + if get_setting("first_setup") == "true": + return render_template('first_setup_main_page.html', get_setting=get_setting) + return redirect(url_for('admin')) + +@app.route('/admin_setup') +def admin_setup(): + if get_setting("first_setup") == "true": + return render_template('admin_setup.html', get_setting=get_setting) + return redirect(url_for('admin')) + +@app.route('/step1', methods=['GET', 'POST']) +def step1(): + form = Step1Form() + step1_settings = ['enable_chat', 'enable_deltachat', 'enable_file_viewer'] + if not form.is_submitted(): + for s in step1_settings: + getattr(form, s).data = (get_setting(s) == "true") + if form.validate_on_submit(): + if form.submit.data: + for s in step1_settings: + setting_value = getattr(form, s).data + setting_value = str(setting_value).lower() + set_setting(s, setting_value) + db.session.commit() + return redirect(url_for('step2')) + if get_setting("first_setup") == "true": + return render_template('step1.html', form=form, get_setting=get_setting) + return redirect(url_for('admin')) + + +@app.route('/step2', methods=['GET', 'POST']) +def step2(): + form = Step2Form() + step2_settings = ['butterbox_hostname', 'butterbox_name'] + if not form.is_submitted(): + for s in step2_settings: + getattr(form, s).data = get_setting(s) + if form.validate_on_submit(): + if form.submit.data: + for s in step2_settings: + setting_value = getattr(form, s).data + if s == 'butterbox_hostname': + setting_value = setting_value.lower().replace(" ", "") + set_setting(s, setting_value) + 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: + set_setting('butterbox_logo', new_value) + db.session.commit() + return redirect(url_for('step3')) + if get_setting("first_setup") == "true": + return render_template('step2.html', form=form, get_setting=get_setting) + return redirect(url_for('admin')) +@app.route('/step3', methods=['GET', 'POST']) +def step3(): + form = Step3Form() + + step3_bool_settings = ['enable_wifi_sharing', 'enable_access_point'] + step3_settings = ['ssid', 'wifi_password'] + if not form.is_submitted(): + for s in step3_bool_settings: + getattr(form, s).data = (get_setting(s) == "true") + for s in step3_settings: + getattr(form, s).data = get_setting(s) + if form.validate_on_submit(): + if form.submit.data: + for s in (step3_bool_settings + step3_settings): + setting_value = getattr(form, s).data + set_setting(s, setting_value) + db.session.commit() + return redirect(url_for('step4')) + if get_setting("first_setup") == "true": + return render_template('step3.html', form=form, get_setting=get_setting) + return redirect(url_for('admin')) + + +@app.route('/step4', methods=['GET', 'POST']) +def step4(): + form = Step4Form() + step4_settings = ['root_account_settings', 'ssh_access_settings', 'root_password', 'admin_password'] + if not form.is_submitted(): + for s in step4_settings: + getattr(form, s).data = get_setting(s) + if form.validate_on_submit(): + if form.submit.data: + new_admin_password = form.admin_password.data + if new_admin_password: + admin_user = db.session.scalar(sa.select(User).where(User.username == 'admin')) + if not admin_user.check_password(new_admin_password): + admin_user.set_password(new_admin_password) + db.session.add(admin_user) + step4_settings.remove('admin_password') + for s in step4_settings: + setting_value = getattr(form, s).data + set_setting(s, setting_value) + set_setting('first_setup', "false") + db.session.commit() + return redirect(url_for('setup_complete')) + if get_setting("first_setup") == "true": + return render_template('step4.html', form=form, get_setting=get_setting) + return redirect(url_for('admin')) + +@app.route('/setup_complete') +def setup_complete(): + if get_setting("first_setup"): + dump_settings("settings.txt") + return render_template('setup_complete.html', get_setting=get_setting) + return redirect(url_for('admin')) + + @app.route('/logout') def logout(): logout_user() - return redirect(url_for('admin')) -@app.route('/admin', methods=['GET', 'POST']) -@login_required + return redirect(url_for('admin_setup')) +@app.route('/admin', methods=['GET']) def admin(): + if get_setting("first_setup") == "true": + return redirect(url_for('first_setup')) + return redirect(url_for('admin_settings')) + +@app.route('/admin_settings', methods=['GET', 'POST']) +@login_required +def admin_settings(): raspap_installed = os.path.exists("/var/www/html/raspap") + raspap_installed = True form = SettingsForm() - populate_settings = ['butterbox_name', 'wifi_password', 'ssid', 'butterbox_hostname', 'root_account_settings', 'ssh_access_settings', 'root_password', 'admin_password'] - bool_settings = ['enable_access_point','enable_file_viewer', 'enable_map_viewer', 'enable_app_store', 'enable_chat', 'enable_deltachat', 'enable_wifi_sharing'] + populate_settings = ['butterbox_name', 'wifi_password', 'ssid', 'root_account_settings', 'ssh_access_settings', 'root_password', 'admin_password'] + bool_settings = ['enable_access_point','enable_file_viewer', 'enable_chat', 'enable_deltachat', 'enable_wifi_sharing'] populate_settings.extend(bool_settings) if not form.is_submitted(): for s in populate_settings: if s in bool_settings: getattr(form, s).data = (get_setting(s) == "true") else: - print(s, get_setting(s)) getattr(form, s).data = get_setting(s) non_admin_settings_changed = False + if not form.validate_on_submit(): + print(form.errors) if form.validate_on_submit(): if form.submit.data: for s in populate_settings: @@ -215,7 +338,6 @@ def admin(): set_setting('apply_changes', "true") dump_settings("settings.txt") flash(_("⚠️ Changes applied! If needed, the system will restart. This may take up to two minutes.")) - return render_template('admin.html', raspap_installed=raspap_installed, get_setting=get_setting, form=form) diff --git a/app/templates/admin.html b/app/templates/admin.html index 4c466cc..e5df09d 100644 --- a/app/templates/admin.html +++ b/app/templates/admin.html @@ -19,11 +19,6 @@ - -
{{ wtf.form_bool_field(form.enable_chat) }}

Whether Matrix chat services are enabled.

@@ -36,10 +31,7 @@ {{ wtf.form_bool_field(form.enable_file_viewer) }}

Whether files services via USB are enabled.

- +
@@ -49,11 +41,6 @@

This is the name shown in the UI. Current name: {{ get_setting('butterbox_name') }}, accessed at {{ get_setting('butterbox_name') }}.local.

-
- {{ wtf.form_input_field(form.butterbox_hostname, form.butterbox_hostname.errors) }} -

This is the URL used to access the box by adding .local in your browser. - Current hostname: {{ get_setting('butterbox_hostname') }}.local.

-
{{ form.butterbox_logo(class='label', style="width: 280px") }}
diff --git a/app/templates/admin_setup.html b/app/templates/admin_setup.html new file mode 100644 index 0000000..2eaf9d4 --- /dev/null +++ b/app/templates/admin_setup.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} +{% import "bulma_wtf.html" as wtf %} + +

{{ _("Admin Settings")}}

+ +
+ +

01 {{ _("Choose Services") }}

+
+ +

02 {{ _("Customise Portal") }}

+
+ +

03 {{ _("Secure Portal") }}

+
+ +

04 {{ _("Secure Admin Settings") }}

+
+
+{% endblock %} + diff --git a/app/templates/base.html b/app/templates/base.html index 3ca375b..0465826 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -31,7 +31,7 @@