aboutsummaryrefslogtreecommitdiff
path: root/src/hydrilla/proxy/web_ui
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2022-08-17 13:50:34 +0200
committerWojtek Kosior <koszko@koszko.org>2022-09-28 12:54:13 +0200
commitd54a95e0f9c689f2bbaaea90a3a16a855a408823 (patch)
tree2fcd758c6eaa7bc65a9744969a506c4ed24e0365 /src/hydrilla/proxy/web_ui
parent2c98d04e4d4a344dc04a481b039a235678f7848e (diff)
downloadhaketilo-hydrilla-d54a95e0f9c689f2bbaaea90a3a16a855a408823.tar.gz
haketilo-hydrilla-d54a95e0f9c689f2bbaaea90a3a16a855a408823.zip
allow loading packages from zip files through web UI and listing installed mappings
Diffstat (limited to 'src/hydrilla/proxy/web_ui')
-rw-r--r--src/hydrilla/proxy/web_ui/_app.py13
-rw-r--r--src/hydrilla/proxy/web_ui/packages.py89
-rw-r--r--src/hydrilla/proxy/web_ui/root.py19
-rw-r--r--src/hydrilla/proxy/web_ui/templates/base.html.jinja14
-rw-r--r--src/hydrilla/proxy/web_ui/templates/packages.html.jinja78
-rw-r--r--src/hydrilla/proxy/web_ui/templates/packages__load_from_disk.html.jinja14
-rw-r--r--src/hydrilla/proxy/web_ui/templates/packages__show_single.html.jinja32
7 files changed, 248 insertions, 11 deletions
diff --git a/src/hydrilla/proxy/web_ui/_app.py b/src/hydrilla/proxy/web_ui/_app.py
new file mode 100644
index 0000000..d5783d1
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/_app.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: CC0-1.0
+
+# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
+#
+# Available under the terms of Creative Commons Zero v1.0 Universal.
+
+import flask
+
+from .. import state as st
+
+
+class WebUIApp(flask.Flask):
+ _haketilo_state: st.HaketiloState
diff --git a/src/hydrilla/proxy/web_ui/packages.py b/src/hydrilla/proxy/web_ui/packages.py
index 7d67b63..a618ca0 100644
--- a/src/hydrilla/proxy/web_ui/packages.py
+++ b/src/hydrilla/proxy/web_ui/packages.py
@@ -31,14 +31,99 @@
# Enable using with Python 3.7.
from __future__ import annotations
+import tempfile
+import zipfile
import typing as t
+from urllib.parse import urlparse
+from pathlib import Path
+
import flask
+import werkzeug
+
+from ...exceptions import HaketiloException
+from ...translations import smart_gettext as _
+from .. import state as st
+from . import _app
+
+
+class InvalidUploadedMalcontent(HaketiloException):
+ def __init__(self):
+ super().__init__(_('err.proxy.uploaded_malcontent_invalid'))
bp = flask.Blueprint('load_packages', __package__)
-@bp.route('/packages/load_from_disk')
-def load_from_disk() -> flask.Response:
+@bp.route('/packages/load_from_disk', methods=['GET'])
+def load_from_disk_get() -> flask.Response:
html = flask.render_template('packages__load_from_disk.html.jinja')
return flask.make_response(html, 200)
+
+@bp.route('/packages/load_from_disk', methods=['POST'])
+def load_from_disk_post() -> werkzeug.Response:
+ parsed_url = urlparse(flask.request.referrer)
+ if parsed_url.netloc != 'hkt.mitm.it':
+ return load_from_disk_get()
+
+ zip_file_storage = flask.request.files.get('packages_zipfile')
+ if zip_file_storage is None:
+ return load_from_disk_get()
+
+ with tempfile.TemporaryDirectory() as tmpdir_str:
+ tmpdir = Path(tmpdir_str)
+ tmpdir_child = tmpdir / 'childdir'
+ tmpdir_child.mkdir()
+
+ try:
+ with zipfile.ZipFile(zip_file_storage) as zip_file:
+ zip_file.extractall(tmpdir_child)
+ except:
+ raise HaketiloException(_('err.proxy.uploaded_file_not_zip'))
+
+ extracted_top_level_files = tuple(tmpdir_child.iterdir())
+ if extracted_top_level_files == ():
+ raise InvalidUploadedMalcontent()
+
+ if len(extracted_top_level_files) == 1 and \
+ extracted_top_level_files[0].is_dir():
+ malcontent_dir_path = extracted_top_level_files[0]
+ else:
+ malcontent_dir_path = tmpdir_child
+
+ state = t.cast(_app.WebUIApp, flask.current_app)._haketilo_state
+
+ try:
+ state.import_packages(malcontent_dir_path)
+ except:
+ raise InvalidUploadedMalcontent()
+
+ return flask.redirect(flask.url_for('.packages'))
+
+@bp.route('/packages')
+def packages() -> flask.Response:
+ state = t.cast(_app.WebUIApp, flask.current_app)._haketilo_state
+
+ display_infos = state.mapping_version_store().get_display_infos()
+ sorted_infos = sorted(display_infos, key=(lambda di: di.info))
+
+ html = flask.render_template(
+ 'packages.html.jinja',
+ display_infos = sorted_infos
+ )
+ return flask.make_response(html, 200)
+
+@bp.route('/packages/view/<string:mapping_id>')
+def show_package(mapping_id: str) -> flask.Response:
+ state = t.cast(_app.WebUIApp, flask.current_app)._haketilo_state
+
+ try:
+ store = state.mapping_version_store()
+ display_info = store.get(mapping_id).get_display_info()
+
+ html = flask.render_template(
+ 'packages__show_single.html.jinja',
+ display_info = display_info
+ )
+ return flask.make_response(html, 200)
+ except st.MissingItemError:
+ flask.abort(404)
diff --git a/src/hydrilla/proxy/web_ui/root.py b/src/hydrilla/proxy/web_ui/root.py
index 194251e..64d6be1 100644
--- a/src/hydrilla/proxy/web_ui/root.py
+++ b/src/hydrilla/proxy/web_ui/root.py
@@ -37,33 +37,36 @@ from threading import Lock
import jinja2
import flask
-from ...translations import smart_gettext as _
+from ...translations import translation as make_translation
+from ... import versions
from .. import state as st
from .. import http_messages
-
from . import repos
from . import packages
+from . import _app
-class WebUIApp(flask.Flask):
+class WebUIAppImpl(_app.WebUIApp):
def __init__(self):
super().__init__(__name__)
self.jinja_options = {
**self.jinja_options,
'loader': jinja2.PackageLoader(__package__),
- 'autoescape': jinja2.select_autoescape()
+ 'autoescape': jinja2.select_autoescape(['html.jinja']),
+ 'extensions': [
+ *self.jinja_options.get('extensions', []),
+ 'jinja2.ext.i18n'
+ ]
}
for blueprint in [repos.bp, packages.bp]:
self.register_blueprint(blueprint)
- _haketilo_state: st.HaketiloState
-
# Flask app is not thread-safe and has to be accompanied by an ugly lock. This
# can cause slow requests to block other requests, so we might need a better
# workaround at some later point.
-app = WebUIApp()
+app = WebUIAppImpl()
app_lock = Lock()
@@ -83,6 +86,8 @@ def process_request(
with app_lock:
app._haketilo_state = state
+ app.jinja_env.install_gettext_translations(make_translation())
+
flask_response = app.test_client().open(
path = path,
base_url = 'https://hkt.mitm.it',
diff --git a/src/hydrilla/proxy/web_ui/templates/base.html.jinja b/src/hydrilla/proxy/web_ui/templates/base.html.jinja
index c6f0dcf..4a9adf8 100644
--- a/src/hydrilla/proxy/web_ui/templates/base.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/base.html.jinja
@@ -23,7 +23,19 @@ in a proprietary work, I am not going to enforce this in court.
<html>
<head>
{% block head %}
- <title>{% block title required %}{% endblock %} - Haketilo proxy</title>
+ <title>{% block title required %}{% endblock %} - Haketilo proxy</title>
+ <style>
+ {% block style %}
+ body {
+ color: #444;
+ }
+
+ #main {
+ max-width: 750px;
+ margin: auto;
+ }
+ {% endblock %}
+ </style>
{% endblock %}
</head>
<body>
diff --git a/src/hydrilla/proxy/web_ui/templates/packages.html.jinja b/src/hydrilla/proxy/web_ui/templates/packages.html.jinja
new file mode 100644
index 0000000..d0ba5cb
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/templates/packages.html.jinja
@@ -0,0 +1,78 @@
+{#
+Spdx-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+
+Proxy web UI package list 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 %}Available packages{% endblock %}
+{% block style %}
+{{ super() }}
+
+ul#packages_list {
+ padding: 0;
+}
+
+ul#packages_list > li {
+ list-style-type: none;
+ max-width: 100%;
+ overflow-x: scroll;
+ white-space: nowrap;
+ padding: 5px;
+ margin: 5px;
+ border: 2px solid #999;
+ border-radius: 5px;
+}
+
+ul#packages_list > li > a {
+ display: block;
+ text-decoration: inherit;
+ color: inherit;
+}
+
+.package-identifier {
+ font-size: 80%;
+ color: #555;
+}
+
+{% endblock %}
+{% block main %}
+ <h3>{{ _('web_ui.h3.packages') }}</h3>
+ <ul id="packages_list">
+ {% for info in display_infos %}
+ <li
+ {% if info.info.repo == '<local>' %}
+ class="package-entry-local"
+ {% endif %}
+ >
+ <a href="{{ url_for('.show_package', mapping_id=info.ref.id) }}">
+ <div>
+ {{ info.info.long_name }}
+ </div>
+ <div class="package-identifier">
+ {{ info.info.versioned_identifier }}
+ {% if info.info.repo != '<local>' %}
+ @
+ {{ info.info.repo }}
+ {% endif %}
+ </div>
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
+{% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/packages__load_from_disk.html.jinja b/src/hydrilla/proxy/web_ui/templates/packages__load_from_disk.html.jinja
index 07ed3b3..52280b2 100644
--- a/src/hydrilla/proxy/web_ui/templates/packages__load_from_disk.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/packages__load_from_disk.html.jinja
@@ -22,5 +22,17 @@ in a proprietary work, I am not going to enforce this in court.
{% extends "base.html.jinja" %}
{% block title %}Load package{% endblock %}
{% block main %}
- Not implemented yet :(
+ <form method="POST" enctype="multipart/form-data">
+ <div>
+ <label for="packages_zipfile">
+ Select a ZIP file with packages&apos; &quot;malcontent&quot; directory.
+ </label>
+ </div>
+ <div>
+ <input id="packages_zipfile" name="packages_zipfile" type="file" required="">
+ </div>
+ <div>
+ <button>Install packages</button>
+ </div>
+ </form>
{% endblock %}
diff --git a/src/hydrilla/proxy/web_ui/templates/packages__show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/packages__show_single.html.jinja
new file mode 100644
index 0000000..5e20dd7
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/templates/packages__show_single.html.jinja
@@ -0,0 +1,32 @@
+{#
+Spdx-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+
+Proxy web UI package show 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 %} Package details {% endblock %}
+{% block style %}
+{{ super() }}
+{% endblock %}
+{% block main %}
+ <h3>{{ _('web_ui.h3.package_{}').format(display_info.info.long_name) }}</h3>
+ <div class="package-identifier">
+ {{ display_info.info.versioned_identifier }}
+ </div>
+{% endblock %}