aboutsummaryrefslogtreecommitdiff
path: root/src/hydrilla/proxy/web_ui
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2022-08-18 19:18:00 +0200
committerWojtek Kosior <koszko@koszko.org>2022-09-28 12:54:22 +0200
commite1344ae7017b28a54d7714895bd54c8431a20bc6 (patch)
tree66bfcb166a87afa10a0b45100231c102385baf08 /src/hydrilla/proxy/web_ui
parent2579081df2a568192887d776a6965af323b7c4ee (diff)
downloadhaketilo-hydrilla-e1344ae7017b28a54d7714895bd54c8431a20bc6.tar.gz
haketilo-hydrilla-e1344ae7017b28a54d7714895bd54c8431a20bc6.zip
allow adding, removing and altering repositories
This commit also temporarily breaks package import from files :/
Diffstat (limited to 'src/hydrilla/proxy/web_ui')
-rw-r--r--src/hydrilla/proxy/web_ui/_app.py5
-rw-r--r--src/hydrilla/proxy/web_ui/packages.py23
-rw-r--r--src/hydrilla/proxy/web_ui/repos.py84
-rw-r--r--src/hydrilla/proxy/web_ui/root.py18
-rw-r--r--src/hydrilla/proxy/web_ui/templates/base.html.jinja6
-rw-r--r--src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja4
-rw-r--r--src/hydrilla/proxy/web_ui/templates/packages.html.jinja11
-rw-r--r--src/hydrilla/proxy/web_ui/templates/packages__load_from_disk.html.jinja5
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos.html.jinja7
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos__add.html.jinja62
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos__show_single.html.jinja40
11 files changed, 211 insertions, 54 deletions
diff --git a/src/hydrilla/proxy/web_ui/_app.py b/src/hydrilla/proxy/web_ui/_app.py
index d5783d1..ab15918 100644
--- a/src/hydrilla/proxy/web_ui/_app.py
+++ b/src/hydrilla/proxy/web_ui/_app.py
@@ -4,6 +4,8 @@
#
# Available under the terms of Creative Commons Zero v1.0 Universal.
+import typing as t
+
import flask
from .. import state as st
@@ -11,3 +13,6 @@ from .. import state as st
class WebUIApp(flask.Flask):
_haketilo_state: st.HaketiloState
+
+def get_haketilo_state() -> st.HaketiloState:
+ return t.cast(WebUIApp, flask.current_app)._haketilo_state
diff --git a/src/hydrilla/proxy/web_ui/packages.py b/src/hydrilla/proxy/web_ui/packages.py
index f38ea13..90876aa 100644
--- a/src/hydrilla/proxy/web_ui/packages.py
+++ b/src/hydrilla/proxy/web_ui/packages.py
@@ -35,7 +35,6 @@ import tempfile
import zipfile
import typing as t
-from urllib.parse import urlparse
from pathlib import Path
import flask
@@ -55,16 +54,12 @@ class InvalidUploadedMalcontent(HaketiloException):
bp = flask.Blueprint('load_packages', __package__)
@bp.route('/packages/load_from_disk', methods=['GET'])
-def load_from_disk_get() -> flask.Response:
+def load_from_disk_get() -> werkzeug.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()
@@ -90,31 +85,27 @@ def load_from_disk_post() -> werkzeug.Response:
else:
malcontent_dir_path = tmpdir_child
- state = t.cast(_app.WebUIApp, flask.current_app)._haketilo_state
-
try:
- state.import_packages(malcontent_dir_path)
+ _app.get_haketilo_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
+def packages() -> werkzeug.Response:
+ store = _app.get_haketilo_state().mapping_version_store()
html = flask.render_template(
'packages.html.jinja',
- display_infos = state.mapping_version_store().get_display_infos()
+ display_infos = store.get_display_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
-
+def show_package(mapping_id: str) -> werkzeug.Response:
try:
- store = state.mapping_version_store()
+ store = _app.get_haketilo_state().mapping_version_store()
display_info = store.get(mapping_id).get_display_info()
html = flask.render_template(
diff --git a/src/hydrilla/proxy/web_ui/repos.py b/src/hydrilla/proxy/web_ui/repos.py
index d4e81c0..166cf53 100644
--- a/src/hydrilla/proxy/web_ui/repos.py
+++ b/src/hydrilla/proxy/web_ui/repos.py
@@ -34,6 +34,7 @@ from __future__ import annotations
import typing as t
import flask
+import werkzeug
from .. import state as st
from . import _app
@@ -41,11 +42,36 @@ from . import _app
bp = flask.Blueprint('repos', __package__)
+@bp.route('/repos/add', methods=['GET'])
+def add_repo_get(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response:
+ html = flask.render_template('repos__add.html.jinja', **errors)
+ return flask.make_response(html, 200)
+
+@bp.route('/repos/add', methods=['POST'])
+def add_repo_post() -> werkzeug.Response:
+ form_data = flask.request.form
+ if 'name' not in form_data or 'url' not in form_data:
+ return add_repo_get()
+
+ try:
+ new_repo_ref = _app.get_haketilo_state().repo_store().add(
+ name = form_data['name'],
+ url = form_data['url']
+ )
+ except st.RepoNameInvalid:
+ return add_repo_get({'repo_name_invalid': True})
+ except st.RepoNameTaken:
+ return add_repo_get({'repo_name_taken': True})
+ except st.RepoUrlInvalid:
+ return add_repo_get({'repo_url_invalid': True})
+
+ return flask.redirect(flask.url_for('.show_repo', repo_id=new_repo_ref.id))
+
@bp.route('/repos')
-def repos() -> flask.Response:
- state = t.cast(_app.WebUIApp, flask.current_app)._haketilo_state
+def repos() -> werkzeug.Response:
+ repo_store = _app.get_haketilo_state().repo_store()
- local_semirepo_info, *repo_infos = state.repo_store().get_display_infos()
+ local_semirepo_info, *repo_infos = repo_store.get_display_infos()
html = flask.render_template(
'repos.html.jinja',
@@ -54,12 +80,10 @@ def repos() -> flask.Response:
)
return flask.make_response(html, 200)
-@bp.route('/repos/view/<string:repo_id>', methods=['GET'])
-def show_repo(repo_id: str) -> flask.Response:
- state = t.cast(_app.WebUIApp, flask.current_app)._haketilo_state
-
+@bp.route('/repos/view/<string:repo_id>')
+def show_repo(repo_id: str) -> werkzeug.Response:
try:
- store = state.repo_store()
+ store = _app.get_haketilo_state().repo_store()
display_info = store.get(repo_id).get_display_info()
html = flask.render_template(
@@ -70,6 +94,44 @@ def show_repo(repo_id: str) -> flask.Response:
except st.MissingItemError:
flask.abort(404)
-@bp.route('/repos/view/<string:repo_id>', methods=['POST'])
-def update_repo(repo_id: str) -> flask.Response:
- raise NotImplementedError()
+def sanitize_altered_repo_id(repo_id: str) -> str:
+ repo_id = str(int(repo_id))
+ if repo_id == '1':
+ # Protect local semi-repo.
+ flask.abort(403)
+
+ return repo_id
+
+@bp.route('/repos/update_url/<string:repo_id>', methods=['POST'])
+def update_repo_url(repo_id: str) -> werkzeug.Response:
+ repo_id = sanitize_altered_repo_id(repo_id)
+
+ try:
+ repo_ref = _app.get_haketilo_state().repo_store().get(repo_id)
+ repo_ref.update()
+ except st.MissingItemError:
+ flask.abort(404)
+
+ return flask.redirect(flask.url_for('.show_repo', repo_id=repo_id))
+
+@bp.route('/repos/remove/<string:repo_id>', methods=['POST'])
+def remove_repo(repo_id: str):
+ repo_id = sanitize_altered_repo_id(repo_id)
+
+ try:
+ _app.get_haketilo_state().repo_store().get(repo_id).remove()
+ except st.MissingItemError:
+ flask.abort(404)
+
+ return flask.redirect(flask.url_for('.repos'))
+
+@bp.route('/repos/refresh/<string:repo_id>', methods=['POST'])
+def refresh_repo(repo_id: str):
+ repo_id = sanitize_altered_repo_id(repo_id)
+
+ try:
+ _app.get_haketilo_state().repo_store().get(repo_id).refresh()
+ except st.MissingItemError:
+ flask.abort(404)
+
+ return flask.redirect(flask.url_for('.show_repo', repo_id=repo_id))
diff --git a/src/hydrilla/proxy/web_ui/root.py b/src/hydrilla/proxy/web_ui/root.py
index 64d6be1..0f42981 100644
--- a/src/hydrilla/proxy/web_ui/root.py
+++ b/src/hydrilla/proxy/web_ui/root.py
@@ -32,10 +32,13 @@
from __future__ import annotations
import typing as t
+
from threading import Lock
+from urllib.parse import urlparse
import jinja2
import flask
+import werkzeug
from ...translations import translation as make_translation
from ... import versions
@@ -46,6 +49,17 @@ from . import packages
from . import _app
+def authenticate_by_referrer() -> t.Optional[werkzeug.Response]:
+ if flask.request.method == 'GET':
+ return None
+
+ parsed_url = urlparse(flask.request.referrer)
+ if parsed_url.netloc == 'hkt.mitm.it':
+ return None
+
+ flask.abort(403)
+
+
class WebUIAppImpl(_app.WebUIApp):
def __init__(self):
super().__init__(__name__)
@@ -60,6 +74,8 @@ class WebUIAppImpl(_app.WebUIApp):
]
}
+ self.before_request(authenticate_by_referrer)
+
for blueprint in [repos.bp, packages.bp]:
self.register_blueprint(blueprint)
@@ -71,7 +87,7 @@ app_lock = Lock()
@app.route('/')
-def respond():
+def respond() -> str:
return flask.render_template('root.html.jinja')
diff --git a/src/hydrilla/proxy/web_ui/templates/base.html.jinja b/src/hydrilla/proxy/web_ui/templates/base.html.jinja
index bca5948..c7a0c15 100644
--- a/src/hydrilla/proxy/web_ui/templates/base.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/base.html.jinja
@@ -45,6 +45,12 @@ in a proprietary work, I am not going to enforce this in court.
color: #555;
}
+ .error-note {
+ display: block;
+ border-left: 5px solid #a33;
+ background-color: #fcc;
+ }
+
.hide {
display: none !important;
}
diff --git a/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja b/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja
index b4c8edc..471725b 100644
--- a/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja
@@ -30,7 +30,7 @@ input.chbx-tricks-hide-show:not(:checked)+*+* {
display: none !important;
}
-input.chbx-tricks-hide-show:checked+*+*,
-input.chbx-tricks-show-hide:not(:checked)+*+* {
+input.chbx-tricks-hide-show:checked+*,
+input.chbx-tricks-show-hide:not(:checked)+* {
display: none !important;
}
diff --git a/src/hydrilla/proxy/web_ui/templates/packages.html.jinja b/src/hydrilla/proxy/web_ui/templates/packages.html.jinja
index 48ef80b..bcb8dea 100644
--- a/src/hydrilla/proxy/web_ui/templates/packages.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/packages.html.jinja
@@ -30,11 +30,12 @@ in a proprietary work, I am not going to enforce this in court.
<h3>{{ _('web_ui.packages.heading') }}</h3>
<ul id="item_list">
{% for info in display_infos %}
- <li
- {% if info.info.repo == '<local>' %}
- class="package-entry-local"
- {% endif %}
- >
+ {% if info.info.repo == '<local>' -%}
+ {%- set entry_classes = 'package-entry-local' -%}
+ {%- else -%}
+ {%- set entry_classes = '' -%}
+ {%- endif -%}
+ <li class="{{ entry_classes }}">
<a href="{{ url_for('.show_package', mapping_id=info.ref.id) }}">
<div>
{{ info.info.long_name }}
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 52280b2..33a99f4 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
@@ -20,7 +20,7 @@ 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 %}Load package{% endblock %}
+{% block title %} {{ _('web_ui.packages.load_from_disk.title') }} {% endblock %}
{% block main %}
<form method="POST" enctype="multipart/form-data">
<div>
@@ -29,7 +29,8 @@ in a proprietary work, I am not going to enforce this in court.
</label>
</div>
<div>
- <input id="packages_zipfile" name="packages_zipfile" type="file" required="">
+ <input id="packages_zipfile" name="packages_zipfile" type="file"
+ required="">
</div>
<div>
<button>Install packages</button>
diff --git a/src/hydrilla/proxy/web_ui/templates/repos.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos.html.jinja
index 94b7e2b..05c948e 100644
--- a/src/hydrilla/proxy/web_ui/templates/repos.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/repos.html.jinja
@@ -28,6 +28,11 @@ in a proprietary work, I am not going to enforce this in court.
{% endblock %}
{% block main %}
<h3>{{ _('web_ui.repos.heading') }}</h3>
+ <div>
+ <a href="{{ url_for('.add_repo_get') }}" class="button">
+ {{ _('web_ui.repos.add_repo_button') }}
+ </a>
+ </div>
<ul id="item_list">
{% for info in display_infos %}
<li
@@ -51,7 +56,7 @@ in a proprietary work, I am not going to enforce this in court.
</li>
{% endfor %}
<li>
- <a href="{{ url_for('.show_repo', repo_id=1) }}">
+ <a href="{{ url_for('.show_repo', repo_id=local_semirepo_info.ref.id) }}">
{{ _('web_ui.repos.local_packages_semirepo') }}
<div class="small-print">
{{
diff --git a/src/hydrilla/proxy/web_ui/templates/repos__add.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos__add.html.jinja
new file mode 100644
index 0000000..cbe7105
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/templates/repos__add.html.jinja
@@ -0,0 +1,62 @@
+{#
+Spdx-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+
+Proxy web UI repo creation 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.repos.add.title') }} {% endblock %}
+{% block main %}
+ <h3>{{ _('web_ui.repos.add.heading') }}</h3>
+ <form method="POST">
+ <div>
+ <label for="name_field">
+ {{ _('web_ui.repos.add.name_field_label') }}
+ </label>
+ </div>
+ {% if repo_name_invalid is defined -%}
+ <aside class="error-note">
+ {{ _('web_ui.repos.add.repo_name_invalid') }}
+ </aside>
+ {%- endif %}
+ {% if repo_name_taken is defined -%}
+ <aside class="error-note">
+ {{ _('web_ui.repos.add.repo_name_taken') }}
+ </aside>
+ {%- endif %}
+ <div>
+ <input id="name_field" name="name" required="">
+ </div>
+ <div>
+ <label for="url_field">
+ {{ _('web_ui.repos.add.url_field_label') }}
+ </label>
+ </div>
+ {% if repo_url_invalid is defined -%}
+ <aside class="error-note">
+ {{ _('web_ui.repos.add.repo_url_invalid') }}
+ </aside>
+ {%- endif %}
+ <div>
+ <input id="url_field" name="url" required="">
+ </div>
+ <div>
+ <button class="button">{{ _('web_ui.repos.add.submit_button') }}</button>
+ </div>
+ </form>
+{% 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 9abb00d..96100ce 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
@@ -27,21 +27,19 @@ in a proprietary work, I am not going to enforce this in court.
{% include 'include/checkbox_tricks_style.css.jinja' %}
{% endblock %}
{% block main %}
+ {% set repo_id = display_info.ref.id -%}
<h3>
- {% if display_info.ref.id != '1' %}
- {{
- _('web_ui.repos.single.heading.name_{}')
- .format(display_info.info.long_name)
- }}
- {% else %}
+ {% if display_info.is_local_semirepo -%}
{{ _('web_ui.repos.local_packages_semirepo') }}
- {% endif %}
+ {% else -%}
+ {{ _('web_ui.repos.single.heading.name_{}').format(display_info.name) }}
+ {% endif -%}
</h3>
- {% if display_info.deleted == True %}
+ {% if display_info.deleted and not display_info.is_local_semirepo -%}
<div>
{{ _('web_ui.repos.single.repo_is_deleted') }}
</div>
- {% elif display_info.deleted == False %}
+ {% elif not display_info.deleted -%}
<input id="show_url_edit_form" type="checkbox" class="chbx-tricks-show-hide"
checked="">
<div>
@@ -54,7 +52,8 @@ in a proprietary work, I am not going to enforce this in court.
</label>
</div>
</div>
- <form method="POST">
+ {% set action_url = url_for('.update_repo_url', repo_id=repo_id) -%}
+ <form method="POST" action="{{ action_url }}">
<input type="hidden" name="action" value="update_url">
<div>
<input name="url" value="{{ display_info.url }}">
@@ -69,22 +68,23 @@ in a proprietary work, I am not going to enforce this in court.
</div>
</form>
<div>
- {% if display_info.last_refreshed is None %}
+ {% if display_info.last_refreshed is none -%}
{{ _('web_ui.repos.single.repo_never_refreshed') }}
- {% else %}
+ {% else -%}
{{
_('web_ui.repos.single.last_refreshed_{}')
.format(display_info.last_refreshed.strftime('%F %H:%M'))
}}
- {% endif %}
- <form method="POST">
+ {% endif -%}
+ {% set action_url = url_for('.refresh_repo', repo_id=repo_id) -%}
+ <form method="POST" action="{{ action_url }}">
<input type="hidden" name="action" value="refresh_repo">
<button class="green-button">
{{ _('web_ui.repos.single.refresh_now_button') }}
</button>
</form>
</div>
- {% endif %}
+ {% endif -%}
<div>
{{
_('web_ui.repos.item_count_{mappings}_{resources}')
@@ -94,4 +94,12 @@ in a proprietary work, I am not going to enforce this in court.
)
}}
</div>
-{% endblock %}
+ {% if not display_info.is_local_semirepo -%}
+ {% set action_url = url_for('.remove_repo', repo_id=repo_id) -%}
+ <form method="POST" action="{{ action_url }}">
+ <button class="green-button">
+ {{ _('web_ui.repos.single.remove_button') }}
+ </button>
+ </form>
+ {% endif -%}
+{% endblock -%}