diff options
Diffstat (limited to 'src/hydrilla/proxy/web_ui')
10 files changed, 355 insertions, 58 deletions
diff --git a/src/hydrilla/proxy/web_ui/options.py b/src/hydrilla/proxy/web_ui/options.py new file mode 100644 index 0000000..f24c356 --- /dev/null +++ b/src/hydrilla/proxy/web_ui/options.py @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# Proxy web UI options page. +# +# This file is part of Hydrilla&Haketilo. +# +# Copyright (C) 2022 Wojtek Kosior +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# +# I, Wojtek Kosior, thereby promise not to sue for violation of this +# file's license. Although I request that you do not make use this code +# in a proprietary program, I am not going to enforce this in court. + +# Enable using with Python 3.7. +from __future__ import annotations + +import typing as t + +import flask +import werkzeug + +from .. import state as st +from . import _app + + +bp = flask.Blueprint('options', __package__) + +@bp.route('/options', methods=['GET']) +def options(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: + html = flask.render_template( + 'options.html.jinja', + settings = _app.get_haketilo_state().get_settings(), + **errors + ) + return flask.make_response(html, 200) + +@bp.route('/options', methods=['POST']) +def options_post() -> werkzeug.Response: + action = flask.request.form['action'] + + state = _app.get_haketilo_state() + + if action == 'use_enabled': + state.update_settings(mapping_use_mode=st.MappingUseMode.WHEN_ENABLED) + elif action == 'use_auto': + state.update_settings(mapping_use_mode=st.MappingUseMode.AUTO) + elif action == 'use_question': + state.update_settings(mapping_use_mode=st.MappingUseMode.QUESTION) + elif action == 'allow_scripts': + state.update_settings(default_allow_scripts=True) + elif action == 'block_scripts': + state.update_settings(default_allow_scripts=False) + else: + raise ValueError() + + return flask.redirect(flask.url_for('.options'), 303) diff --git a/src/hydrilla/proxy/web_ui/prompts.py b/src/hydrilla/proxy/web_ui/prompts.py index b546e47..58b7906 100644 --- a/src/hydrilla/proxy/web_ui/prompts.py +++ b/src/hydrilla/proxy/web_ui/prompts.py @@ -42,30 +42,37 @@ from . import _app bp = flask.Blueprint('prompts', __package__) -@bp.route('/auto_install_error', methods=['GET']) -def auto_install_error_prompt() -> werkzeug.Response: - state = _app.get_haketilo_state() +def deserialized_request_details(salt: str) -> t.Mapping[str, str]: serializer = URLSafeSerializer( - state.get_secret(), - salt = 'auto_install_error' + _app.get_haketilo_state().get_secret(), + salt = salt ) + return serializer.loads(flask.request.args['details']) + + +@bp.route('/auto_install_error', methods=['GET']) +def auto_install_error_prompt(errors: t.Mapping[str, bool] = {}) \ + -> werkzeug.Response: try: - details: t.Mapping[str, str] = \ - serializer.loads(flask.request.args['details']) + details = deserialized_request_details('auto_install_error') except: return flask.redirect(flask.url_for('home')) try: - payload_ref = state.payload_store().get(details['payload_id']) + payload_store = _app.get_haketilo_state().payload_store() + payload_ref = payload_store.get(details['payload_id']) display_info = payload_ref.get_display_info() + if not display_info.has_problems: + return flask.redirect(details['next_url']) + html = flask.render_template( 'prompts/auto_install_error.html.jinja', display_info = display_info, - next_url = details['next_url'] + **errors ) return flask.make_response(html, 200) except st.MissingItemError: @@ -73,12 +80,16 @@ def auto_install_error_prompt() -> werkzeug.Response: @bp.route('/auto_install_error', methods=['POST']) def auto_install_error_prompt_post() -> werkzeug.Response: + try: + details = deserialized_request_details('auto_install_error') + except: + return flask.redirect(flask.url_for('home'), code=303) + form_data = flask.request.form action = form_data['action'] mapping_ver_id = str(int(form_data['mapping_ver_id'])) - payload_id = str(int(form_data['payload_id'])) - next_url = form_data['next_url'] + payload_id = str(int(details['payload_id'])) state = _app.get_haketilo_state() @@ -92,24 +103,81 @@ def auto_install_error_prompt_post() -> werkzeug.Response: if action == 'disable_mapping': mapping_ver_ref.update_mapping_status(st.EnabledStatus.DISABLED) elif action == 'retry_install': - payload_ref.ensure_items_installed() + payload_ref.ensure_items_installed() else: raise ValueError() - except (st.RepoCommunicationError, st.FileInstallationError): - params = {'payload_id': payload_id, 'next_url': next_url} + except st.RepoCommunicationError: + assert action == 'retry_install' + return auto_install_error_prompt({'repo_communication_error': True}) + except st.FileInstallationError: + assert action == 'retry_install' + return auto_install_error_prompt({'file_installation_error': True}) + except st.MissingItemError: + flask.abort(404) - serializer = URLSafeSerializer( - state.get_secret(), - salt = 'auto_install_error' - ) - query = urlencode({'details': params}) - redirect_url = flask.url_for( - '.auto_install_error_prompt', - details = serializer.dumps(params) + return flask.redirect(details['next_url']) + + +@bp.route('/package_suggestion', methods=['GET']) +def package_suggestion_prompt(errors: t.Mapping[str, bool] = {}) \ + -> werkzeug.Response: + try: + details = deserialized_request_details('package_suggestion') + except: + return flask.redirect(flask.url_for('home')) + + try: + payload_store = _app.get_haketilo_state().payload_store() + payload_ref = payload_store.get(details['payload_id']) + + display_info = payload_ref.get_display_info() + + if display_info.mapping_info.active != st.ActiveStatus.AUTO: + return flask.redirect(details['next_url']) + + html = flask.render_template( + 'prompts/package_suggestion.html.jinja', + display_info = display_info, + **errors ) + return flask.make_response(html, 200) + except st.MissingItemError: + flask.abort(404) + +@bp.route('/package_suggestion', methods=['POST']) +def package_suggestion_prompt_post() -> werkzeug.Response: + try: + details = deserialized_request_details('package_suggestion') + except: + return flask.redirect(flask.url_for('home')) + + form_data = flask.request.form + action = form_data['action'] - return flask.redirect(redirect_url) + mapping_ver_id = str(int(form_data['mapping_ver_id'])) + + state = _app.get_haketilo_state() + + try: + mapping_ver_store = state.mapping_version_store() + mapping_ver_ref = mapping_ver_store.get(mapping_ver_id) + + if action == 'disable_mapping': + mapping_ver_ref.update_mapping_status(st.EnabledStatus.DISABLED) + elif action == 'enable_mapping': + mapping_ver_ref.update_mapping_status( + enabled = st.EnabledStatus.ENABLED, + frozen = st.FrozenStatus.EXACT_VERSION + ) + else: + raise ValueError() + except st.RepoCommunicationError: + assert action == 'enable_mapping' + return package_suggestion_prompt({'repo_communication_error': True}) + except st.FileInstallationError: + assert action == 'enable_mapping' + return package_suggestion_prompt({'file_installation_error': True}) except st.MissingItemError: flask.abort(404) - return flask.redirect(next_url) + return flask.redirect(details['next_url']) diff --git a/src/hydrilla/proxy/web_ui/root.py b/src/hydrilla/proxy/web_ui/root.py index ff7c1f7..855345e 100644 --- a/src/hydrilla/proxy/web_ui/root.py +++ b/src/hydrilla/proxy/web_ui/root.py @@ -44,6 +44,7 @@ from ...translations import translation as make_translation from ... import item_infos from .. import state as st from .. import http_messages +from . import options from . import repos from . import items from . import prompts @@ -89,10 +90,11 @@ class WebUIAppImpl(_app.WebUIApp): self.jinja_env.globals['InstalledStatus'] = st.InstalledStatus self.jinja_env.globals['ActiveStatus'] = st.ActiveStatus self.jinja_env.globals['ItemType'] = item_infos.ItemType + self.jinja_env.globals['MappingUseMode'] = st.MappingUseMode self.before_request(authenticate_by_referrer) - for blueprint in [repos.bp, items.bp, prompts.bp]: + for blueprint in [repos.bp, items.bp, prompts.bp, options.bp]: self.register_blueprint(blueprint) # Flask app is not thread-safe and has to be accompanied by an ugly lock. This diff --git a/src/hydrilla/proxy/web_ui/templates/base.html.jinja b/src/hydrilla/proxy/web_ui/templates/base.html.jinja index 918fb28..06af7d9 100644 --- a/src/hydrilla/proxy/web_ui/templates/base.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/base.html.jinja @@ -20,9 +20,9 @@ in a proprietary work, I am not going to enforce this in court. -#} <!DOCTYPE html> -{% macro button_row(buttons_data) %} +{% macro button_row(buttons_data, common_fields={}) %} <div class="flex-row"> - {% for classes, text, action in buttons_data %} + {% for classes, text, extra_fields in buttons_data %} {% if not loop.first %} <div class="button-row-separator"></div> {% do classes.append('button-bordering-left') %} @@ -39,7 +39,13 @@ in a proprietary work, I am not going to enforce this in court. {% endif %} <form method="POST" class="flex-row"> - <input name="action" value="{{ action }}" type="hidden"> + {% for name, value in extra_fields.items() %} + <input name="{{ name }}" value="{{ value }}" type="hidden"> + {% endfor %} + {% for name, value in common_fields.items() %} + <input name="{{ name }}" value="{{ value }}" type="hidden"> + {% endfor %} + <button class="{{ classes|join(' ') }}"{{ disabled_attr }}> {{ text }} </button> @@ -92,6 +98,10 @@ in a proprietary work, I am not going to enforce this in court. background-color: #fcc; } + #main > .error-note:first-child { + margin-top: 10px; + } + .block-with-bottom-margin, .flex-row, aside, p { display: block; margin: 0 0 10px 0; @@ -226,6 +236,7 @@ in a proprietary work, I am not going to enforce this in court. {% set navigation_bar = [ ('home', _('web_ui.base.nav.home')), + ('options.options', _('web_ui.base.nav.options')), ('items.packages', _('web_ui.base.nav.packages')), ('items.libraries', _('web_ui.base.nav.libraries')), ('repos.repos', _('web_ui.base.nav.repos')), diff --git a/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja index f1d34cc..edfb772 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja @@ -91,10 +91,14 @@ in a proprietary work, I am not going to enforce this in court. {% do uninstall_but_classes.append('disabled-button') %} {% endif %} {% endif %}{# else/ version_display_info.installed == InstalledStatus.FA... #} + + {% set uninstall_fields = {'action': 'uninstall_item_version'} %} + {% set install_fields = {'action': 'install_item_version'} %} + {{ button_row([ - (uninstall_but_classes, uninstall_text, 'uninstall_item_version'), - (install_but_classes, install_text, 'install_item_version') + (uninstall_but_classes, uninstall_text, uninstall_fields), + (install_but_classes, install_text, install_fields) ]) }} {% endblock main_info %} diff --git a/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja index 1eb9878..c9448e7 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja @@ -89,9 +89,9 @@ in a proprietary work, I am not going to enforce this in court. {{ button_row([ - (disable_but_classes, disable_text, 'disable_item'), - (unenable_but_classes, unenable_text, 'unenable_item'), - (enable_but_classes, enable_text, 'enable_item_version') + (disable_but_classes, disable_text, {'action': 'disable_item'}), + (unenable_but_classes, unenable_text, {'action': 'unenable_item'}), + (enable_but_classes, enable_text, {'action': 'enable_item_version'}) ]) }} @@ -182,9 +182,9 @@ in a proprietary work, I am not going to enforce this in court. {{ button_row([ - (unpin_but_classes, unpin_text, 'unfreeze_item'), - (pin_repo_but_classes, pin_repo_text, 'freeze_to_repo'), - (pin_ver_but_classes, pin_ver_text, 'freeze_to_version') + (unpin_but_classes, unpin_text, {'action': 'unfreeze_item'}), + (pin_repo_but_classes, pin_repo_text, {'action': 'freeze_to_repo'}), + (pin_ver_but_classes, pin_ver_text, {'action': 'freeze_to_version'}) ]) }} diff --git a/src/hydrilla/proxy/web_ui/templates/options.html.jinja b/src/hydrilla/proxy/web_ui/templates/options.html.jinja new file mode 100644 index 0000000..69fc1b0 --- /dev/null +++ b/src/hydrilla/proxy/web_ui/templates/options.html.jinja @@ -0,0 +1,86 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy web UI settings page. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior + +Dual licensed under +* GNU General Public License v3.0 or later and +* Creative Commons Attribution Share Alike 4.0 International. + +You can choose to use either of these licenses or both. + + +I, Wojtek Kosior, thereby promise not to sue for violation of this +file's licenses. Although I request that you do not make use this code +in a proprietary work, I am not going to enforce this in court. +#} +{% extends "base.html.jinja" %} + +{% block title %} {{ _('web_ui.options.title') }} {% endblock %} + +{% block main %} + <h3> + {{ _('web_ui.options.heading') }} + </h3> + + {% set use_enabled_but_classes = ['green-button'] %} + {% set use_auto_but_classes = ['green-button'] %} + {% set use_question_but_classes = ['green-button'] %} + + <p> + {% if settings.mapping_use_mode == MappingUseMode.WHEN_ENABLED %} + {% do use_enabled_but_classes.append('disabled-button') %} + {{ _('web_ui.options.packages_are_used_when_enabled') }} + {% elif settings.mapping_use_mode == MappingUseMode.QUESTION %} + {% do use_question_but_classes.append('disabled-button') %} + {{ _('web_ui.options.user_gets_asked_whether_to_enable_package') }} + {% else %} + {# settings.mapping_use_mode == MappingUseMode.AUTO #} + {% do use_auto_but_classes.append('disabled-button') %} + {{ _('web_ui.options.packages_are_used_automatically') }} + {% endif %} + </p> + + {{ + button_row([ + (use_enabled_but_classes, + _('web_ui.options.use_enabled_button'), + {'action': 'use_enabled'}), + (use_question_but_classes, + _('web_ui.options.use_question_button'), + {'action': 'use_question'}), + (use_auto_but_classes, + _('web_ui.options.use_auto_button'), + {'action': 'use_auto'}) + ]) + }} + + <div class="horizontal-separator"></div> + + {% set allow_but_classes = ['red-button'] %} + {% set block_but_classes = ['green-button'] %} + + <p> + {% if settings.default_allow_scripts %} + {% do allow_but_classes.append('disabled-button') %} + {{ _('web_ui.options.scripts_are_allowed_by_default') }} + {% else %} + {% do block_but_classes.append('disabled-button') %} + {{ _('web_ui.options.scripts_are_blocked_by_default') }} + {% endif %} + </p> + + {% set allow_but_text = _('web_ui.options.allow_scripts_button') %} + {% set block_but_text = _('web_ui.options.block_scripts_button') %} + + {{ + button_row([ + (allow_but_classes, allow_but_text, {'action': 'allow_scripts'}), + (block_but_classes, block_but_text, {'action': 'block_scripts'}) + ]) + }} +{% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja b/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja index 01f5c19..82a12e5 100644 --- a/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/prompts/auto_install_error.html.jinja @@ -24,19 +24,15 @@ in a proprietary work, I am not going to enforce this in court. {{ _('web_ui.prompts.auto_install_error.title') }} {% endblock %} -{% macro button_form(action, button_class, button_text) %} - <form class="flex-row" method="POST"> - <input name="next_url" value="{{ next_url }}" type="hidden"> - <input name="payload_id" value="{{ display_info.ref.id }}" type="hidden"> - {% set mapping_ver_id = display_info.mapping_info.ref.id %} - <input name="mapping_ver_id" value="{{ mapping_ver_id }}" type="hidden"> - - <input name="action" value="{{ action }}" type="hidden"> - <button class="{{ button_class }}">{{ button_text }}</button> - </form> -{% endmacro %} - {% block main %} + {% if file_installation_error is defined %} + {{ error_note(_('web_ui.err.retry_install.file_installation_error')) }} + {% endif %} + + {% if repo_communication_error is defined %} + {{ error_note(_('web_ui.err.retry_install.repo_communication_error')) }} + {% endif %} + <h3> {{ _('web_ui.prompts.auto_install_error.heading') }} </h3> @@ -48,13 +44,14 @@ in a proprietary work, I am not going to enforce this in court. }} </p> - <div class="flex-row"> - {% set but_text = _('web_ui.prompts.auto_install_error.disable_button') %} - {{ button_form('disable_mapping', 'red-button', but_text) }} - - <div class="button-row-separator"></div> + {% set disable_text = _('web_ui.prompts.auto_install_error.disable_button') %} + {% set retry_text = _('web_ui.prompts.auto_install_error.retry_button') %} - {% set but_text = _('web_ui.prompts.auto_install_error.retry_button') %} - {{ button_form('retry_install', 'green-button', but_text) }} - </div> + {{ + button_row([ + (['red-button'], disable_text, {'action': 'disable_mapping'}), + (['green-button'], retry_text, {'action': 'retry_install'}) + ], {'mapping_ver_id': display_info.mapping_info.ref.id} + ) + }} {% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja b/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja new file mode 100644 index 0000000..ea906bb --- /dev/null +++ b/src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja @@ -0,0 +1,58 @@ +{# +SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + +Proxy web UI page that asks whether to enable a package that can be used with +current site. + +This file is part of Hydrilla&Haketilo. + +Copyright (C) 2022 Wojtek Kosior + +Dual licensed under +* GNU General Public License v3.0 or later and +* Creative Commons Attribution Share Alike 4.0 International. + +You can choose to use either of these licenses or both. + + +I, Wojtek Kosior, thereby promise not to sue for violation of this +file's licenses. Although I request that you do not make use this code +in a proprietary work, I am not going to enforce this in court. +#} +{% extends "base.html.jinja" %} + +{% block title %} + {{ _('web_ui.prompts.package_suggestion.title') }} +{% endblock %} + +{% block main %} + {% if file_installation_error is defined %} + {{ error_note(_('web_ui.err.file_installation_error')) }} + {% endif %} + + {% if repo_communication_error is defined %} + {{ error_note(_('web_ui.err.repo_communication_error')) }} + {% endif %} + + <h3> + {{ _('web_ui.prompts.package_suggestion.heading') }} + </h3> + + <p> + {{ + _('web_ui.prompts.package_suggestion.do_you_want_to_enable_package_{}') + .format(display_info.mapping_info.info.long_name) + }} + </p> + + {% set disable_text = _('web_ui.prompts.package_suggestion.disable_button') %} + {% set enable_text = _('web_ui.prompts.package_suggestion.enable_button') %} + + {{ + button_row([ + (['red-button'], disable_text, {'action': 'disable_mapping'}), + (['blue-button'], enable_text, {'action': 'enable_mapping'}) + ], {'mapping_ver_id': display_info.mapping_info.ref.id} + ) + }} +{% endblock %} diff --git a/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja index 448c451..2c695d0 100644 --- a/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja @@ -152,7 +152,8 @@ in a proprietary work, I am not going to enforce this in court. </p> {% set button_text = _('web_ui.repos.single.refresh_now_button') %} - {{ button_row([[['green-button'], button_text, 'refresh_repo']]) }} + {% set extra_fields = {'action': 'refresh_repo'} %} + {{ button_row([(['green-button'], button_text, extra_fields)]) }} </div> <div class="horizontal-separator"></div> @@ -171,6 +172,7 @@ in a proprietary work, I am not going to enforce this in court. <div class="horizontal-separator"></div> {% set button_text = _('web_ui.repos.single.remove_button') %} - {{ button_row([[['green-button'], button_text, 'remove_repo']]) }} + {% set extra_fields = {'action': 'remove_repo'} %} + {{ button_row([(['green-button'], button_text, extra_fields)]) }} {% endif %} {% endblock %} |