From 48f80ae480e2fc0eabbdb5041e841b80c0f788f4 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Thu, 29 Sep 2022 12:52:54 +0200 Subject: [proxy] display some more details in mapping/resource version view in the web UI --- src/hydrilla/item_infos.py | 2 + src/hydrilla/locales/en_US/LC_MESSAGES/messages.po | 124 ++++++++---- src/hydrilla/proxy/policies/payload_resource.py | 2 +- src/hydrilla/proxy/state.py | 58 +++++- src/hydrilla/proxy/state_impl/items.py | 209 +++++++++++++++++++++ src/hydrilla/proxy/state_impl/payloads.py | 2 +- src/hydrilla/proxy/web_ui/items.py | 129 +++++++++++++ .../templates/include/item_list_style.css.jinja | 18 +- .../web_ui/templates/items/item_view.html.jinja | 6 +- .../templates/items/item_viewversion.html.jinja | 108 ++++++++++- .../web_ui/templates/items/libraries.html.jinja | 4 +- .../templates/items/library_viewversion.html.jinja | 32 +++- .../templates/items/package_viewversion.html.jinja | 54 +++++- .../web_ui/templates/items/packages.html.jinja | 4 +- .../proxy/web_ui/templates/repos/index.html.jinja | 2 +- .../proxy/web_ui/templates/rules/index.html.jinja | 2 +- 16 files changed, 675 insertions(+), 81 deletions(-) diff --git a/src/hydrilla/item_infos.py b/src/hydrilla/item_infos.py index 9dfad14..c8c5deb 100644 --- a/src/hydrilla/item_infos.py +++ b/src/hydrilla/item_infos.py @@ -156,6 +156,7 @@ class ItemInfoBase(ABC, ItemIdentity, Categorizable): source_copyright: tuple[FileSpecifier, ...] = dc.field(hash=False, compare=False) uuid: t.Optional[str] = dc.field(hash=False, compare=False) long_name: str = dc.field(hash=False, compare=False) + description: str = dc.field(hash=False, compare=False) allows_eval: bool = dc.field(hash=False, compare=False) allows_cors_bypass: bool = dc.field(hash=False, compare=False) required_mappings: tuple[ItemSpecifier, ...] = dc.field(hash=False, compare=False) @@ -209,6 +210,7 @@ class ItemInfoBase(ABC, ItemIdentity, Categorizable): identifier = item_obj['identifier'], uuid = item_obj.get('uuid'), long_name = item_obj['long_name'], + description = item_obj['description'], allows_eval = eval_perm, allows_cors_bypass = cors_bypass_perm, required_mappings = required_mappings, diff --git a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po index bb93bdf..c479412 100644 --- a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po +++ b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: hydrilla 2.0\n" "Report-Msgid-Bugs-To: koszko@koszko.org\n" -"POT-Creation-Date: 2022-09-29 12:29+0200\n" +"POT-Creation-Date: 2022-10-04 21:19+0200\n" "PO-Revision-Date: 2022-02-12 00:00+0000\n" "Last-Translator: Wojtek Kosior \n" "Language: en_US\n" @@ -137,7 +137,7 @@ msgstr "" msgid "err.item_info.filename_invalid_{}" msgstr "Item definition conatains an illegal path: {}" -#: src/hydrilla/item_infos.py:482 +#: src/hydrilla/item_infos.py:484 #, python-brace-format msgid "uuid_mismatch_{identifier}" msgstr "Two different uuids were specified for item '{identifier}'." @@ -455,43 +455,67 @@ msgstr "" msgid "web_ui.home.prune_orphans_button" msgstr "Prune orphans" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:25 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:69 #: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:30 #: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:35 msgid "web_ui.err.file_installation_error" msgstr "Failed to install needed items from repository." -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:29 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:73 msgid "web_ui.err.uninstall_disallowed" msgstr "This item is required and cannot be uninstalled." -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:33 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:77 #: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:34 #: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:39 msgid "web_ui.err.repo_communication_error" msgstr "Couldn't communicate with repository." -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:38 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:82 +msgid "web_ui.items.single_version.identifier_label" +msgstr "Identifier" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:90 msgid "web_ui.items.single_version.version_label" msgstr "Version" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:58 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:99 +msgid "web_ui.items.single_version.uuid_label" +msgstr "UUID" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:109 +msgid "web_ui.items.single_version.description_label" +msgstr "Description" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:118 +msgid "web_ui.items.single_version.licenses_label" +msgstr "License and copyright files" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:124 +msgid "web_ui.items.single_version.no_license_files" +msgstr "There are no designated files with legal information." + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:131 +msgid "web_ui.items.single_version.reqired_mappings_label" +msgstr "Required packages" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:158 msgid "web_ui.items.single_version.install_uninstall_label" msgstr "Installation status" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:65 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:165 msgid "web_ui.items.single_version.retry_install_button" msgstr "Retry installation" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:69 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:169 msgid "web_ui.items.single_version.leave_uninstalled_button" msgstr "Leave uninstalled" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:73 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:173 msgid "web_ui.items.single_version.install_button" msgstr "Install" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:75 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:175 msgid "web_ui.items.single_version.uninstall_button" msgstr "Uninstall" @@ -543,23 +567,35 @@ msgstr "Library is not currently installed." msgid "web_ui.items.single_version.library.version_list_heading" msgstr "Other available versions of the library" +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:58 +msgid "web_ui.items.single_version.library.scripts_label" +msgstr "Scripts" + #: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:64 +msgid "web_ui.items.single_version.library.no_script_files" +msgstr "There are no JavaScript files in this library." + +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:71 +msgid "web_ui.items.single_version.library.deps_label" +msgstr "Dependencies" + +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:86 msgid "web_ui.items.single_version.library.enabled_label" msgstr "Usage status" -#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:68 +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:90 msgid "web_ui.items.single_version.library.item_required" msgstr "This library version is required by an enabled package." -#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:73 +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:95 msgid "web_ui.items.single_version.library.item_not_activated" msgstr "This library version is not used by any package enabled by the user." -#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:75 +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:97 msgid "web_ui.items.single_version.library.item_will_be_asked_about" msgstr "This library version is not used by any package enabled by the user." -#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:78 +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:100 msgid "web_ui.items.single_version.library.item_auto_activated" msgstr "" "This library version is used by some package. The package has not been " @@ -606,101 +642,109 @@ msgstr "Package is not currently installed." msgid "web_ui.items.single_version.package.version_list_heading" msgstr "Other available versions of the package" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:64 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:58 +msgid "web_ui.items.single_version.package.payloads_label" +msgstr "Payloads" + +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:91 +msgid "web_ui.items.single_version.package.no_payloads" +msgstr "This package has no payloads." + +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:97 msgid "web_ui.items.single_version.package.enabled_label" msgstr "Usage status" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:70 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:103 msgid "web_ui.items.single_version.unenable_button" msgstr "Forget" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:71 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:104 msgid "web_ui.items.single_version.disable_button" msgstr "Disable" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:72 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:105 msgid "web_ui.items.single_version.enable_button" msgstr "Enable" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:81 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:114 msgid "web_ui.items.single_version.package.item_not_activated" msgstr "This package is not enabled. This version won't be used." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:83 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:116 msgid "web_ui.items.single_version.package.item_will_be_asked_about" msgstr "" "This package is not currently enabled. You will be asked whether to " "enable this version of it when you visit a website where it can be used." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:86 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:119 msgid "web_ui.items.single_version.package.item_auto_activated" msgstr "" "This package version has not been explicitly enabled but it is going to " "be used automatically." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:90 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:123 msgid "web_ui.items.single_version.package.item_disabled" msgstr "All versions of the package have been explicitly disabled by the user." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:94 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:127 msgid "web_ui.items.single_version.package.item_enabled" msgstr "The package has been enabled by the user." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:109 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:142 msgid "web_ui.items.single_version.package.pinning_label" msgstr "Enabled package pinning" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:115 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:148 msgid "web_ui.items.single_version.unpin_button" msgstr "Unpin" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:120 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:153 msgid "web_ui.items.single_version.not_pinned" msgstr "The package is not pinned to any version." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:125 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:158 msgid "web_ui.items.single_version.pinned_repo_local" msgstr "The package is pinned to only use locally installed versions." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:128 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:161 msgid "web_ui.items.single_version.pinned_repo_{}" msgstr "The package is pinned to only use versions from repository '{}'." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:139 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:172 msgid "web_ui.items.single_version.pin_local_repo_button" msgstr "Pin to local packages" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:144 -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:157 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:177 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:190 msgid "web_ui.items.single_version.pin_repo_button" msgstr "Pin to repository" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:151 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:184 msgid "web_ui.items.single_version.repin_repo_button" msgstr "Pin to this repository" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:165 -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:176 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:198 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:209 msgid "web_ui.items.single_version.pin_ver_button" msgstr "Pin to this version" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:168 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:201 msgid "web_ui.items.single_version.pinned_ver" msgstr "The package is pinned to this version." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:171 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:204 msgid "web_ui.items.single_version.repin_ver_button" msgstr "Pin to this version" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:173 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:206 msgid "web_ui.items.single_version.pinned_other_ver" msgstr "The package is pinned to a different version." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:181 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:214 msgid "web_ui.items.single_version.active_ver_is_this_one" msgstr "This is the currently active version." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:185 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:218 msgid "web_ui.items.single_version.active_ver_is_{}" msgstr "Currently active version is '{}'." diff --git a/src/hydrilla/proxy/policies/payload_resource.py b/src/hydrilla/proxy/policies/payload_resource.py index 3872037..30b28f2 100644 --- a/src/hydrilla/proxy/policies/payload_resource.py +++ b/src/hydrilla/proxy/policies/payload_resource.py @@ -91,7 +91,7 @@ class PayloadResourcePolicy(PayloadAwarePolicy): return http_messages.ProducedResponse( 200, - ((b'Content-Type', file_data.type.encode()),), + ((b'Content-Type', file_data.mime_type.encode()),), file_data.contents ) diff --git a/src/hydrilla/proxy/state.py b/src/hydrilla/proxy/state.py index 559a546..4142f7f 100644 --- a/src/hydrilla/proxy/state.py +++ b/src/hydrilla/proxy/state.py @@ -258,6 +258,13 @@ class RepoIterationRef(Ref): pass +@dc.dataclass(frozen=True) +class FileData: + mime_type: str + name: str + contents: bytes + + @dc.dataclass(frozen=True) class MappingDisplayInfo(item_infos.CorrespondsToMappingDCMixin): ref: 'MappingRef' @@ -300,6 +307,10 @@ class MappingStore(Store[MappingRef]): def get_display_infos(self) -> t.Sequence[MappingDisplayInfo]: ... + @abstractmethod + def get_by_identifier(self, identifier: str) -> MappingRef: + ... + @dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc] class MappingVersionRef(Ref, item_infos.CorrespondsToMappingDCMixin): @abstractmethod @@ -322,6 +333,23 @@ class MappingVersionRef(Ref, item_infos.CorrespondsToMappingDCMixin): ) -> None: ... + @abstractmethod + def get_license_file(self, name: str) -> FileData: + ... + + @abstractmethod + def get_upstream_license_file_url(self, name: str) -> str: + ... + + @abstractmethod + def get_required_mapping(self, identifier: str) -> 'MappingVersionRef': + ... + + @abstractmethod + def get_payload_resource(self, pattern: str, identifier: str) \ + -> 'ResourceVersionRef': + ... + @abstractmethod def get_item_display_info(self) -> RichMappingDisplayInfo: ... @@ -359,6 +387,10 @@ class ResourceStore(Store[ResourceRef]): def get_display_infos(self) -> t.Sequence[ResourceDisplayInfo]: ... + @abstractmethod + def get_by_identifier(self, identifier: str) -> ResourceRef: + ... + @dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc] class ResourceVersionRef(Ref, item_infos.CorrespondsToResourceDCMixin): @@ -370,6 +402,26 @@ class ResourceVersionRef(Ref, item_infos.CorrespondsToResourceDCMixin): def uninstall(self) -> t.Optional['ResourceVersionRef']: ... + @abstractmethod + def get_license_file(self, name: str) -> FileData: + ... + + @abstractmethod + def get_resource_file(self, name: str) -> FileData: + ... + + @abstractmethod + def get_upstream_license_file_url(self, name: str) -> str: + ... + + @abstractmethod + def get_upstream_resource_file_url(self, name: str) -> str: + ... + + @abstractmethod + def get_dependency(self, identifier: str) -> ResourceVersionRef: + ... + @abstractmethod def get_item_display_info(self) -> RichResourceDisplayInfo: ... @@ -409,12 +461,6 @@ class PayloadDisplayInfo: pattern: str has_problems: bool -@dc.dataclass(frozen=True) -class FileData: - type: str - name: str - contents: bytes - @dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc] class PayloadRef(Ref): """....""" diff --git a/src/hydrilla/proxy/state_impl/items.py b/src/hydrilla/proxy/state_impl/items.py index d312db9..010b8ca 100644 --- a/src/hydrilla/proxy/state_impl/items.py +++ b/src/hydrilla/proxy/state_impl/items.py @@ -39,12 +39,30 @@ import typing as t import dataclasses as dc from contextlib import contextmanager +from urllib.parse import urljoin from ... import item_infos from .. import state as st from . import base +def _get_item_id(cursor: sqlite3.Cursor, item_type: str, identifier: str) \ + -> str: + cursor.execute( + 'SELECT item_id FROM items WHERE identifier = ? AND type = ?;', + (identifier, item_type) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (item_id,), = rows + + return str(item_id) + + def _get_parent_item_id(cursor: sqlite3.Cursor, version_id: str) -> str: cursor.execute( ''' @@ -66,6 +84,7 @@ def _get_parent_item_id(cursor: sqlite3.Cursor, version_id: str) -> str: return str(item_id) + def _set_installed_status(cursor: sqlite3.Cursor, id: str, new_status: str) \ -> None: cursor.execute( @@ -138,6 +157,71 @@ def _uninstall_version(ref: VersionRefVar) -> t.Optional[VersionRefVar]: return ref if version_still_present else None +def _get_file(ref: VersionRefVar, name: str, file_type: str = 'L') \ + -> st.FileData: + with ref.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + f.data, fu.mime_type + FROM + item_versions AS iv + JOIN items AS i USING (item_id) + JOIN file_uses AS fu USING (item_version_id) + JOIN files AS f USING (file_id) + WHERE + (iv.item_version_id = ? AND iv.installed = 'I') AND + i.type = ? AND + (fu.name = ? AND fu.type = ?) AND + f.data IS NOT NULL; + ''', + (ref.id, ref.type.value[0].upper(), name, file_type) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (data, mime_type), = rows + + return st.FileData(mime_type, name, data) + + +def _get_upstream_file_url( + ref: VersionRefVar, + name: str, + file_type: str = 'L' +) -> str: + with ref.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + f.sha256, r.url + FROM + item_versions AS iv + JOIN repo_iterations AS ri USING(repo_iteration_id) + JOIN repos AS r USING(repo_id) + JOIN file_uses AS fu USING(item_version_id) + JOIN files AS f USING(file_id) + WHERE + iv.item_version_id = ? AND + (fu.name = ? AND fu.type = ?) AND + r.url IS NOT NULL; + ''', + (ref.id, name, file_type) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (sha256, repo_url), = rows + + return urljoin(repo_url, f'file/sha256/{sha256}') + + @dc.dataclass(frozen=True, unsafe_hash=True) class ConcreteMappingRef(st.MappingRef): state: base.HaketiloStateWithFields = dc.field(hash=False, compare=False) @@ -405,6 +489,12 @@ class ConcreteMappingStore(st.MappingStore): return sorted(result, key=(lambda di: di.identifier)) + def get_by_identifier(self, identifier: str) -> st.MappingRef: + with self.state.cursor() as cursor: + item_id = _get_item_id(cursor, 'M', identifier) + + return ConcreteMappingRef(item_id, self.state) + @dc.dataclass(frozen=True, unsafe_hash=True) class ConcreteMappingVersionRef(st.MappingVersionRef): @@ -472,6 +562,76 @@ class ConcreteMappingVersionRef(st.MappingVersionRef): mapping_ref.update_status(enabled, frozen, id_to_pass) + def get_license_file(self, name: str) -> st.FileData: + return _get_file(self, name, 'L') + + def get_upstream_license_file_url(self, name: str) -> str: + return _get_upstream_file_url(self, name, 'L') + + def get_required_mapping(self, identifier: str) \ + -> 'ConcreteMappingVersionRef': + with self.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + iv2.item_version_id + FROM + item_versions AS iv1 + JOIN resolved_required_mappings AS rrm + ON iv1.item_version_id = + rrm.requiring_mapping_id + JOIN item_versions AS iv2 + ON rrm.required_mapping_id = + iv2.item_version_id + JOIN items AS i + ON iv2.item_id = i.item_id + WHERE + iv1.item_version_id = ? AND + i.identifier = ?; + ''', + (self.id, identifier) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (required_id,), = rows + + return ConcreteMappingVersionRef(str(required_id), self.state) + + def get_payload_resource(self, pattern: str, identifier: str) \ + -> 'ConcreteResourceVersionRef': + with self.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + iv.item_version_id + FROM + payloads AS p + JOIN resolved_depended_resources AS rdd + USING(payload_id) + JOIN item_versions AS iv + ON rdd.resource_item_id = iv.item_version_id + JOIN items AS i + USING (item_id) + WHERE + (p.mapping_item_id = ? AND p.pattern = ?) AND + i.identifier = ?; + ''', + (self.id, pattern, identifier) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (resource_ver_id,), = rows + + return ConcreteResourceVersionRef(str(resource_ver_id), self.state) + def get_item_display_info(self) -> st.RichMappingDisplayInfo: with self._mapping_ref() as mapping_ref: return mapping_ref.get_display_info() @@ -578,6 +738,12 @@ class ConcreteResourceStore(st.ResourceStore): return sorted(result, key=(lambda di: di.identifier)) + def get_by_identifier(self, identifier: str) -> st.ResourceRef: + with self.state.cursor() as cursor: + item_id = _get_item_id(cursor, 'R', identifier) + + return ConcreteResourceRef(item_id, self.state) + @dc.dataclass(frozen=True, unsafe_hash=True) class ConcreteResourceVersionRef(st.ResourceVersionRef): @@ -589,6 +755,49 @@ class ConcreteResourceVersionRef(st.ResourceVersionRef): def uninstall(self) -> t.Optional['ConcreteResourceVersionRef']: return _uninstall_version(self) + def get_license_file(self, name: str) -> st.FileData: + return _get_file(self, name, 'L') + + def get_resource_file(self, name: str) -> st.FileData: + return _get_file(self, name, 'W') + + def get_upstream_license_file_url(self, name: str) -> str: + return _get_upstream_file_url(self, name, 'L') + + def get_upstream_resource_file_url(self, name: str) -> str: + return _get_upstream_file_url(self, name, 'W') + + def get_dependency(self, identifier: str) -> st.ResourceVersionRef: + with self.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + iv.item_version_id + FROM + resolved_depended_resources AS rdd1 + JOIN payloads AS p + ON rdd1.payload_id = p.payload_id + JOIN resolved_depended_resources AS rdd2 + ON p.payload_id = rdd2.payload_id + JOIN item_versions AS iv + ON rdd2.resource_item_id = iv.item_version_id + JOIN items AS i + USING (item_id) + WHERE + rdd1.resource_item_id = ? AND i.identifier = ?; + ''', + (self.id, identifier) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (dep_id,), = rows + + return ConcreteResourceVersionRef(str(dep_id), self.state) + def get_item_display_info(self) -> st.RichResourceDisplayInfo: with self.state.cursor() as cursor: resource_id = _get_parent_item_id(cursor, self.id) diff --git a/src/hydrilla/proxy/state_impl/payloads.py b/src/hydrilla/proxy/state_impl/payloads.py index 74b8121..ebc7152 100644 --- a/src/hydrilla/proxy/state_impl/payloads.py +++ b/src/hydrilla/proxy/state_impl/payloads.py @@ -264,7 +264,7 @@ class ConcretePayloadRef(st.PayloadRef): (data, mime_type), = result - return st.FileData(type=mime_type, name=file_name, contents=data) + return st.FileData(mime_type=mime_type, name=file_name, contents=data) @dc.dataclass(frozen=True) diff --git a/src/hydrilla/proxy/web_ui/items.py b/src/hydrilla/proxy/web_ui/items.py index f34b89b..467be2f 100644 --- a/src/hydrilla/proxy/web_ui/items.py +++ b/src/hydrilla/proxy/web_ui/items.py @@ -34,6 +34,8 @@ from __future__ import annotations import typing as t +from urllib.parse import unquote + import flask import werkzeug @@ -231,3 +233,130 @@ def alter_library_version(item_version_id: str) -> werkzeug.Response: @bp.route('/packages/viewversion/', methods=['POST']) def alter_package_version(item_version_id: str) -> werkzeug.Response: return alter_item_version(item_version_id, item_infos.ItemType.MAPPING) + +def show_file( + item_version_id: str, + item_type: item_infos.ItemType, + file_type: str, + name: str, +) -> werkzeug.Response: + if file_type not in ('license', 'web_resource'): + flask.abort(404) + + try: + store = item_version_store(_app.get_haketilo_state(), item_type) + item_version_ref = store.get(item_version_id) + + try: + if file_type == 'license': + file_data = item_version_ref.get_license_file(name) + else: + assert isinstance(item_version_ref, st.ResourceVersionRef) + file_data = item_version_ref.get_resource_file(name) + + return werkzeug.Response( + file_data.contents, + mimetype = file_data.mime_type + ) + except st.MissingItemError: + if file_type == 'license': + url = item_version_ref.get_upstream_license_file_url(name) + else: + assert isinstance(item_version_ref, st.ResourceVersionRef) + url = item_version_ref.get_upstream_resource_file_url(name) + + return flask.redirect(url) + + except st.MissingItemError: + flask.abort(404) + +@bp.route('/packages/viewversion///') +def show_mapping_file(item_version_id: str, file_type: str, name: str) \ + -> werkzeug.Response: + item_type = item_infos.ItemType.MAPPING + return show_file(item_version_id, item_type, file_type, name) + +@bp.route('/libraries/viewversion///') +def show_resource_file(item_version_id: str, file_type: str, name: str) \ + -> werkzeug.Response: + item_type = item_infos.ItemType.RESOURCE + return show_file(item_version_id, item_type, file_type, name) + +@bp.route('/libraries/viewdep//') +def show_library_dep(item_version_id: str, dep_identifier: str) \ + -> werkzeug.Response: + state = _app.get_haketilo_state() + + try: + store = state.resource_version_store() + dep_id = store.get(item_version_id).get_dependency(dep_identifier).id + url = flask.url_for('.show_library_version', item_version_id=dep_id) + except st.MissingItemError: + try: + versionless_store = state.resource_store() + item_ref = versionless_store.get_by_identifier(dep_identifier) + url = flask.url_for('.show_library', item_id=item_ref.id) + except st.MissingItemError: + flask.abort(404) + + return flask.redirect(url) + +@bp.route('//viewrequired//') +def show_required_mapping( + item_type: str, + item_version_id: str, + required_identifier: str +) -> werkzeug.Response: + state = _app.get_haketilo_state() + + if item_type not in ('package', 'library'): + flask.abort(404) + + found = False + + if item_type == 'package': + try: + ref = state.mapping_version_store().get(item_version_id) + mapping_ver_id = ref.get_required_mapping(required_identifier).id + url = flask.url_for( + '.show_package_version', + item_version_id = mapping_ver_id + ) + found = True + except st.MissingItemError: + pass + + if not found: + try: + versionless_store = state.mapping_store() + mapping_ref = versionless_store\ + .get_by_identifier(required_identifier) + url = flask.url_for('.show_package', item_id=mapping_ref.id) + except st.MissingItemError: + flask.abort(404) + + return flask.redirect(url) + +@bp.route('/package/viewpayload///') +def show_payload(item_version_id: str, pattern: str, lib_identifier: str) \ + -> werkzeug.Response: + state = _app.get_haketilo_state() + + try: + ref = state.mapping_version_store().get(item_version_id) + + try: + resource_ver_ref = \ + ref.get_payload_resource(unquote(pattern), lib_identifier) + url = flask.url_for( + '.show_library_version', + item_version_id = resource_ver_ref.id + ) + except st.MissingItemError: + resource_ref = \ + state.resource_store().get_by_identifier(lib_identifier) + url = flask.url_for('.show_library', item_id=resource_ref.id) + except st.MissingItemError: + flask.abort(404) + + return flask.redirect(url) 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 index cbdf225..063cd01 100644 --- 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 @@ -18,36 +18,40 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this file's licenses. Although I request that you do not make use of this code in a proprietary work, I am not going to enforce this in court. #} -ul#item_list { +ul.item-list { padding: 0; } -ul#item_list > li { +ul.item-list > li { list-style-type: none; max-width: 100%; white-space: nowrap; margin: 0; } -ul#item_list > li > :only-child { +ul.item-list > li > :only-child { display: block; padding: 5px; overflow-x: scroll; border-bottom: 2px solid #999; } -ul#item_list > li.entry-line-dashed > :only-child { +ul.item-list > li.entry-line-dashed > :only-child { border-bottom-style: dashed } -ul#item_list > li.entry-line-green > :only-child { +ul.item-list > li.entry-line-green > :only-child { border-color: #4caf50; } -ul#item_list > li.entry-line-blue > :only-child { +ul.item-list > li.entry-line-blue > :only-child { border-color: #504caf; } -ul#item_list > li.entry-line-red > :only-child { +ul.item-list > li.entry-line-red > :only-child { border-color: #af504c; } + +ul.item-list > li.invisible-entry-line > :only-child { + border-color: #fff; +} diff --git a/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja index f048f14..5f8b102 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja @@ -20,7 +20,7 @@ code in a proprietary work, I am not going to enforce this in court. #} {% extends "base.html.jinja" %} -{% macro versioned_identifier_with_repo(info) -%} +{% macro version_with_repo(info) -%} {{ info.info.version_string }} {%- if not info.is_local %} @ @@ -56,7 +56,7 @@ code in a proprietary work, I am not going to enforce this in court. {% endblock %} -