summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2022-08-18 13:40:16 +0200
committerWojtek Kosior <koszko@koszko.org>2022-09-28 12:54:22 +0200
commit2579081df2a568192887d776a6965af323b7c4ee (patch)
tree12491e0ee11568a09891f4d261e9cba60e8a8d52 /src
parentc242a5833d41fdcee6e2b35cff7af8d445b44946 (diff)
downloadhaketilo-hydrilla-2579081df2a568192887d776a6965af323b7c4ee.tar.gz
haketilo-hydrilla-2579081df2a568192887d776a6965af323b7c4ee.zip
make it possible to list all repositories in the web UI
Diffstat (limited to 'src')
-rw-r--r--src/hydrilla/proxy/state.py31
-rw-r--r--src/hydrilla/proxy/state_impl/concrete_state.py22
-rw-r--r--src/hydrilla/proxy/state_impl/mappings.py2
-rw-r--r--src/hydrilla/proxy/state_impl/repos.py151
-rw-r--r--src/hydrilla/proxy/tables.sql31
-rw-r--r--src/hydrilla/proxy/web_ui/packages.py5
-rw-r--r--src/hydrilla/proxy/web_ui/repos.py33
-rw-r--r--src/hydrilla/proxy/web_ui/templates/base.html.jinja34
-rw-r--r--src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja36
-rw-r--r--src/hydrilla/proxy/web_ui/templates/include/item_list_style.css.jinja39
-rw-r--r--src/hydrilla/proxy/web_ui/templates/packages.html.jinja71
-rw-r--r--src/hydrilla/proxy/web_ui/templates/packages__show_single.html.jinja11
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos.html.jinja43
-rw-r--r--src/hydrilla/proxy/web_ui/templates/repos__show_single.html.jinja97
14 files changed, 497 insertions, 109 deletions
diff --git a/src/hydrilla/proxy/state.py b/src/hydrilla/proxy/state.py
index 14d38b6..c3712f2 100644
--- a/src/hydrilla/proxy/state.py
+++ b/src/hydrilla/proxy/state.py
@@ -39,6 +39,7 @@ import typing as t
from pathlib import Path
from abc import ABC, abstractmethod
from enum import Enum
+from datetime import datetime
from immutables import Map
@@ -68,6 +69,9 @@ class Ref:
"""...."""
id: str
+ def __post_init__(self):
+ assert isinstance(self.id, str)
+
RefType = t.TypeVar('RefType', bound=Ref)
@@ -83,14 +87,13 @@ class Store(ABC, t.Generic[RefType]):
class RepoRef(Ref):
"""...."""
@abstractmethod
- def remove(self, state: 'HaketiloState') -> None:
+ def remove(self) -> None:
"""...."""
...
@abstractmethod
def update(
self,
- state: 'HaketiloState',
*,
name: t.Optional[str] = None,
url: t.Optional[str] = None
@@ -99,10 +102,30 @@ class RepoRef(Ref):
...
@abstractmethod
- def refresh(self, state: 'HaketiloState') -> 'RepoIterationRef':
+ def refresh(self) -> 'RepoIterationRef':
"""...."""
...
+ @abstractmethod
+ def get_display_info(self) -> 'RepoDisplayInfo':
+ ...
+
+@dc.dataclass(frozen=True)
+class RepoDisplayInfo:
+ ref: RepoRef
+ name: str
+ url: t.Optional[str]
+ deleted: t.Optional[bool]
+ last_refreshed: t.Optional[datetime]
+ resource_count: int
+ mapping_count: int
+
+class RepoStore(Store[RepoRef]):
+ @abstractmethod
+ def get_display_infos(self, include_deleted: bool = False) -> \
+ t.Iterable[RepoDisplayInfo]:
+ ...
+
@dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc]
class RepoIterationRef(Ref):
@@ -253,7 +276,7 @@ class HaketiloState(ABC):
...
@abstractmethod
- def get_repo(self, repo_id: str) -> RepoRef:
+ def repo_store(self) -> RepoStore:
"""...."""
...
diff --git a/src/hydrilla/proxy/state_impl/concrete_state.py b/src/hydrilla/proxy/state_impl/concrete_state.py
index b2b1033..46e7827 100644
--- a/src/hydrilla/proxy/state_impl/concrete_state.py
+++ b/src/hydrilla/proxy/state_impl/concrete_state.py
@@ -52,28 +52,12 @@ from .. import state as st
from .. import policies
from . import base
from . import mappings
+from . import repos
from .load_packages import load_packages
here = Path(__file__).resolve().parent
-@dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc]
-class ConcreteRepoRef(st.RepoRef):
- def remove(self, state: st.HaketiloState) -> None:
- raise NotImplementedError()
-
- def update(
- self,
- state: st.HaketiloState,
- *,
- name: t.Optional[str] = None,
- url: t.Optional[str] = None
- ) -> ConcreteRepoRef:
- raise NotImplementedError()
-
- def refresh(self, state: st.HaketiloState) -> ConcreteRepoIterationRef:
- raise NotImplementedError()
-
@dc.dataclass(frozen=True, unsafe_hash=True)
class ConcreteRepoIterationRef(st.RepoIterationRef):
@@ -440,8 +424,8 @@ class ConcreteHaketiloState(base.HaketiloStateWithFields):
self.policy_tree = new_policy_tree
self.payloads_data = new_payloads_data
- def get_repo(self, repo_id: str) -> st.RepoRef:
- return ConcreteRepoRef(repo_id)
+ def repo_store(self) -> st.RepoStore:
+ return repos.ConcreteRepoStore(self)
def get_repo_iteration(self, repo_iteration_id: str) -> st.RepoIterationRef:
return ConcreteRepoIterationRef(repo_iteration_id)
diff --git a/src/hydrilla/proxy/state_impl/mappings.py b/src/hydrilla/proxy/state_impl/mappings.py
index 5e31814..3668784 100644
--- a/src/hydrilla/proxy/state_impl/mappings.py
+++ b/src/hydrilla/proxy/state_impl/mappings.py
@@ -130,4 +130,4 @@ class ConcreteMappingVersionStore(st.MappingVersionStore):
info = st.MappingDisplayInfo(ref, item_info, status, is_orphan)
result.append(info)
- return result
+ return sorted(result, key=(lambda di: di.info))
diff --git a/src/hydrilla/proxy/state_impl/repos.py b/src/hydrilla/proxy/state_impl/repos.py
new file mode 100644
index 0000000..be11a88
--- /dev/null
+++ b/src/hydrilla/proxy/state_impl/repos.py
@@ -0,0 +1,151 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# Haketilo proxy data and configuration (RepoRef and RepoStore subtypes).
+#
+# 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.
+
+"""
+This module provides an interface to interact with repositories configured
+inside Haketilo.
+"""
+
+# Enable using with Python 3.7.
+from __future__ import annotations
+
+import typing as t
+import dataclasses as dc
+
+from datetime import datetime
+
+from .. import state as st
+from . import base
+
+def make_repo_display_info(
+ ref: st.RepoRef,
+ name: str,
+ url: t.Optional[str],
+ deleted: t.Optional[bool],
+ last_refreshed: t.Optional[int],
+ resource_count: int,
+ mapping_count: int
+) -> st.RepoDisplayInfo:
+ last_refreshed_converted: t.Optional[datetime] = None
+ if last_refreshed is not None:
+ last_refreshed_converted = datetime.fromtimestamp(last_refreshed)
+
+ return st.RepoDisplayInfo(
+ ref = ref,
+ name = name,
+ url = url,
+ deleted = deleted,
+ last_refreshed = last_refreshed_converted,
+ resource_count = resource_count,
+ mapping_count = mapping_count
+ )
+
+
+@dc.dataclass(frozen=True, unsafe_hash=True)
+class ConcreteRepoRef(st.RepoRef):
+ """...."""
+ state: base.HaketiloStateWithFields = dc.field(hash=False, compare=False)
+
+ def remove(self) -> None:
+ raise NotImplementedError()
+
+ def update(
+ self,
+ *,
+ name: t.Optional[str] = None,
+ url: t.Optional[str] = None
+ ) -> st.RepoRef:
+ raise NotImplementedError()
+
+ def refresh(self) -> st.RepoIterationRef:
+ raise NotImplementedError()
+
+ def get_display_info(self) -> st.RepoDisplayInfo:
+ with self.state.cursor() as cursor:
+ cursor.execute(
+ '''
+ SELECT
+ name, url, deleted, last_refreshed,
+ resource_count, mapping_count
+ FROM
+ repo_display_infos
+ WHERE
+ repo_id = ?;
+ ''',
+ (self.id,)
+ )
+
+ rows = cursor.fetchall()
+
+ if rows == []:
+ raise st.MissingItemError()
+
+ row, = rows
+
+ return make_repo_display_info(self, *row)
+
+
+@dc.dataclass(frozen=True)
+class ConcreteRepoStore(st.RepoStore):
+ state: base.HaketiloStateWithFields
+
+ def get(self, id: str) -> st.RepoRef:
+ return ConcreteRepoRef(id, self.state)
+
+ def get_display_infos(self, include_deleted: bool = False) \
+ -> t.Iterable[st.RepoDisplayInfo]:
+ with self.state.cursor() as cursor:
+ condition: str = 'TRUE'
+ if include_deleted:
+ condition = 'COALESCE(deleted = FALSE, TRUE)'
+
+ cursor.execute(
+ f'''
+ SELECT
+ repo_id, name, url, deleted, last_refreshed,
+ resource_count, mapping_count
+ FROM
+ repo_display_infos
+ WHERE
+ {condition}
+ ORDER BY
+ repo_id != 1, name;
+ '''
+ )
+
+ all_rows = cursor.fetchall()
+
+ assert len(all_rows) > 0 and all_rows[0][0] == 1
+
+ result = []
+ for row in all_rows:
+ repo_id, *rest = row
+
+ ref = ConcreteRepoRef(str(repo_id), self.state)
+
+ result.append(make_repo_display_info(ref, *rest))
+
+ return result
diff --git a/src/hydrilla/proxy/tables.sql b/src/hydrilla/proxy/tables.sql
index 0417613..a915f74 100644
--- a/src/hydrilla/proxy/tables.sql
+++ b/src/hydrilla/proxy/tables.sql
@@ -155,6 +155,20 @@ CREATE TABLE item_versions(
REFERENCES repo_iterations (repo_iteration_id)
);
+CREATE VIEW repo_display_infos
+AS
+SELECT
+ r.repo_id, r.name, r.url, r.deleted, r.last_refreshed,
+ COALESCE(SUM(i.type = 'R'), 0) AS resource_count,
+ COALESCE(SUM(i.type = 'M'), 0) AS mapping_count
+FROM
+ repos AS r
+ LEFT JOIN repo_iterations AS ir USING (repo_id)
+ LEFT JOIN item_versions AS iv USING (repo_iteration_id)
+ LEFT JOIN items AS i USING (item_id)
+GROUP BY
+ r.repo_id, r.name, r.url, r.deleted, r.last_refreshed;
+
CREATE TABLE payloads(
payload_id INTEGER PRIMARY KEY,
@@ -225,23 +239,6 @@ CREATE TABLE resolved_depended_resources(
ON DELETE CASCADE
) WITHOUT ROWID;
--- CREATE TABLE resolved_required_mappings(
--- requiring_item_id INTEGER,
--- required_mapping_item_id INTEGER,
-
--- PRIMARY KEY (requiring_item_id, required_mapping_item_id),
-
--- CHECK (requiring_item_id != required_mapping_item_id),
-
--- FOREIGN KEY (requiring_item_id)
--- REFERENCES items (item_id),
--- -- Note: the referenced mapping shall have installed=TRUE.
--- FOREIGN KEY (required_mapping_item_id)
--- REFERENCES mappings (item_id),
--- FOREIGN KEY (required_mapping_item_id)
--- REFERENCES items (item_id)
--- );
-
CREATE TABLE files(
file_id INTEGER PRIMARY KEY,
diff --git a/src/hydrilla/proxy/web_ui/packages.py b/src/hydrilla/proxy/web_ui/packages.py
index a618ca0..f38ea13 100644
--- a/src/hydrilla/proxy/web_ui/packages.py
+++ b/src/hydrilla/proxy/web_ui/packages.py
@@ -103,12 +103,9 @@ def load_from_disk_post() -> werkzeug.Response:
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
+ display_infos = state.mapping_version_store().get_display_infos()
)
return flask.make_response(html, 200)
diff --git a/src/hydrilla/proxy/web_ui/repos.py b/src/hydrilla/proxy/web_ui/repos.py
index 0f55b2a..d4e81c0 100644
--- a/src/hydrilla/proxy/web_ui/repos.py
+++ b/src/hydrilla/proxy/web_ui/repos.py
@@ -35,10 +35,41 @@ import typing as t
import flask
+from .. import state as st
+from . import _app
+
bp = flask.Blueprint('repos', __package__)
@bp.route('/repos')
def repos() -> flask.Response:
- html = flask.render_template('repos.html.jinja')
+ state = t.cast(_app.WebUIApp, flask.current_app)._haketilo_state
+
+ local_semirepo_info, *repo_infos = state.repo_store().get_display_infos()
+
+ html = flask.render_template(
+ 'repos.html.jinja',
+ local_semirepo_info = local_semirepo_info,
+ display_infos = repo_infos
+ )
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
+
+ try:
+ store = state.repo_store()
+ display_info = store.get(repo_id).get_display_info()
+
+ html = flask.render_template(
+ 'repos__show_single.html.jinja',
+ display_info = display_info
+ )
+ return flask.make_response(html, 200)
+ 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()
diff --git a/src/hydrilla/proxy/web_ui/templates/base.html.jinja b/src/hydrilla/proxy/web_ui/templates/base.html.jinja
index 4a9adf8..bca5948 100644
--- a/src/hydrilla/proxy/web_ui/templates/base.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/base.html.jinja
@@ -25,16 +25,30 @@ in a proprietary work, I am not going to enforce this in court.
{% block head %}
<title>{% block title required %}{% endblock %} - Haketilo proxy</title>
<style>
- {% block style %}
- body {
- color: #444;
- }
-
- #main {
- max-width: 750px;
- margin: auto;
- }
- {% endblock %}
+ {% block style %}
+ body {
+ color: #444;
+ }
+
+ #main {
+ max-width: 750px;
+ margin: auto;
+ }
+
+ a {
+ text-decoration: inherit;
+ color: inherit;
+ }
+
+ .small-print {
+ font-size: 80%;
+ color: #555;
+ }
+
+ .hide {
+ display: none !important;
+ }
+ {% endblock %}
</style>
{% endblock %}
</head>
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
new file mode 100644
index 0000000..b4c8edc
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/templates/include/checkbox_tricks_style.css.jinja
@@ -0,0 +1,36 @@
+{#
+Spdx-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+
+Proxy web UI reusable stylesheet for checkbox-based dynamically displayed
+elements.
+
+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.
+#}
+
+input.chbx-tricks-show-hide, input.chbx-tricks-hide-show {
+ display: none !important;
+}
+
+input.chbx-tricks-show-hide:checked+*+*,
+input.chbx-tricks-hide-show:not(:checked)+*+* {
+ display: none !important;
+}
+
+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/include/item_list_style.css.jinja b/src/hydrilla/proxy/web_ui/templates/include/item_list_style.css.jinja
new file mode 100644
index 0000000..332d5f9
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/templates/include/item_list_style.css.jinja
@@ -0,0 +1,39 @@
+{#
+Spdx-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+
+Proxy web UI reusable stylesheet for lists.
+
+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.
+#}
+ul#item_list {
+ padding: 0;
+}
+
+ul#item_list > li {
+ list-style-type: none;
+ max-width: 100%;
+ overflow-x: scroll;
+ white-space: nowrap;
+ margin: 5px;
+}
+
+ul#item_list > li > :only-child {
+ display: block;
+ padding: 5px;
+ border: 2px solid #999;
+ border-radius: 5px;
+}
diff --git a/src/hydrilla/proxy/web_ui/templates/packages.html.jinja b/src/hydrilla/proxy/web_ui/templates/packages.html.jinja
index d0ba5cb..48ef80b 100644
--- a/src/hydrilla/proxy/web_ui/templates/packages.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/packages.html.jinja
@@ -20,59 +20,34 @@ 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 title %} {{ _('web_ui.packages.title') }} {% 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;
-}
+ {{ super() }}
+ {% include 'include/item_list_style.css.jinja' %}
{% endblock %}
{% block main %}
- <h3>{{ _('web_ui.h3.packages') }}</h3>
- <ul id="packages_list">
+ <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 %}
- >
- <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>
+ <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="small-print">
+ {{ 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__show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/packages__show_single.html.jinja
index 5e20dd7..eb526c4 100644
--- a/src/hydrilla/proxy/web_ui/templates/packages__show_single.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/packages__show_single.html.jinja
@@ -20,12 +20,17 @@ 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 title %} {{ _('web_ui.packages.single.title') }} {% endblock %}
{% block style %}
-{{ super() }}
+ {{ super() }}
{% endblock %}
{% block main %}
- <h3>{{ _('web_ui.h3.package_{}').format(display_info.info.long_name) }}</h3>
+ <h3>
+ {{
+ _('web_ui.packages.single.heading.name_{}')
+ .format(display_info.info.long_name)
+ }}
+ </h3>
<div class="package-identifier">
{{ display_info.info.versioned_identifier }}
</div>
diff --git a/src/hydrilla/proxy/web_ui/templates/repos.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos.html.jinja
index 59eb1bd..94b7e2b 100644
--- a/src/hydrilla/proxy/web_ui/templates/repos.html.jinja
+++ b/src/hydrilla/proxy/web_ui/templates/repos.html.jinja
@@ -20,7 +20,46 @@ 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 %}Repositories{% endblock %}
+{% block title %}{{ _('web_ui.repos.title') }}{% endblock %}
+{% block style %}
+ {{ super() }}
+
+ {% include 'include/item_list_style.css.jinja' %}
+{% endblock %}
{% block main %}
- Not implemented yet :(
+ <h3>{{ _('web_ui.repos.heading') }}</h3>
+ <ul id="item_list">
+ {% for info in display_infos %}
+ <li
+ {% if info.deleted %}
+ class="repo-entry-deleted"
+ {% endif %}
+ >
+ <a href="{{ url_for('.show_repo', repo_id=info.ref.id) }}">
+ <div>
+ {{ info.name }}
+ </div>
+ {% if not info.deleted %}
+ <div class="small-print">
+ {{ info.url }}
+ </div>
+ {% endif %}
+ <div class="small-print">
+ {{ _('web_ui.repos.package_count_{}').format(info.mapping_count) }}
+ </div>
+ </a>
+ </li>
+ {% endfor %}
+ <li>
+ <a href="{{ url_for('.show_repo', repo_id=1) }}">
+ {{ _('web_ui.repos.local_packages_semirepo') }}
+ <div class="small-print">
+ {{
+ _('web_ui.repos.package_count_{}')
+ .format(local_semirepo_info.mapping_count)
+ }}
+ </div>
+ </a>
+ </li>
+ </ul>
{% 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
new file mode 100644
index 0000000..9abb00d
--- /dev/null
+++ b/src/hydrilla/proxy/web_ui/templates/repos__show_single.html.jinja
@@ -0,0 +1,97 @@
+{#
+Spdx-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+
+Proxy web UI repository 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.repos.single.title') }} {% endblock %}
+{% block style %}
+ {{ super() }}
+
+ {% include 'include/checkbox_tricks_style.css.jinja' %}
+{% endblock %}
+{% block main %}
+ <h3>
+ {% if display_info.ref.id != '1' %}
+ {{
+ _('web_ui.repos.single.heading.name_{}')
+ .format(display_info.info.long_name)
+ }}
+ {% else %}
+ {{ _('web_ui.repos.local_packages_semirepo') }}
+ {% endif %}
+ </h3>
+ {% if display_info.deleted == True %}
+ <div>
+ {{ _('web_ui.repos.single.repo_is_deleted') }}
+ </div>
+ {% elif display_info.deleted == False %}
+ <input id="show_url_edit_form" type="checkbox" class="chbx-tricks-show-hide"
+ checked="">
+ <div>
+ <div>
+ {{ display_info.url }}
+ </div>
+ <div>
+ <label for="show_url_edit_form" class="green-button">
+ {{ _('web_ui.repos.single.update_url_button') }}
+ </label>
+ </div>
+ </div>
+ <form method="POST">
+ <input type="hidden" name="action" value="update_url">
+ <div>
+ <input name="url" value="{{ display_info.url }}">
+ </div>
+ <div>
+ <button class="green-button">
+ {{ _('web_ui.repos.single.commit_update_url_button') }}
+ </button>
+ <label for="show_url_edit_form" class="green-button">
+ {{ _('web_ui.repos.single.abort_update_url_button') }}
+ </label>
+ </div>
+ </form>
+ <div>
+ {% if display_info.last_refreshed is None %}
+ {{ _('web_ui.repos.single.repo_never_refreshed') }}
+ {% else %}
+ {{
+ _('web_ui.repos.single.last_refreshed_{}')
+ .format(display_info.last_refreshed.strftime('%F %H:%M'))
+ }}
+ {% endif %}
+ <form method="POST">
+ <input type="hidden" name="action" value="refresh_repo">
+ <button class="green-button">
+ {{ _('web_ui.repos.single.refresh_now_button') }}
+ </button>
+ </form>
+ </div>
+ {% endif %}
+ <div>
+ {{
+ _('web_ui.repos.item_count_{mappings}_{resources}')
+ .format(
+ mappings = display_info.mapping_count,
+ resources = display_info.resource_count
+ )
+ }}
+ </div>
+{% endblock %}