Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7066404
D6186.id22498.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
46 KB
Subscribers
None
D6186.id22498.diff
View Options
diff --git a/assets/src/bundles/browse/swhid-utils.js b/assets/src/bundles/browse/swhid-utils.js
--- a/assets/src/bundles/browse/swhid-utils.js
+++ b/assets/src/bundles/browse/swhid-utils.js
@@ -112,7 +112,7 @@
$('#swh-identifiers').tabSlideOut(tabSlideOptions);
// set the tab visible once the close animation is terminated
- $('#swh-identifiers').css('display', 'block');
+ $('#swh-identifiers').addClass('d-none d-sm-block');
$('.swhid-context-option').trigger('click');
// highlighted code lines changed
diff --git a/assets/src/bundles/webapp/code-highlighting.js b/assets/src/bundles/webapp/code-highlighting.js
--- a/assets/src/bundles/webapp/code-highlighting.js
+++ b/assets/src/bundles/webapp/code-highlighting.js
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018-2019 The Software Heritage developers
+ * Copyright (C) 2018-2021 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
@@ -22,21 +22,35 @@
return lineTd;
}
+// function to highlight a range of lines
+export function highlightLines(first, last) {
+ if (!first) {
+ return;
+ }
+ if (!last) {
+ last = first;
+ }
+ for (let i = first; i <= last; ++i) {
+ highlightLine(i);
+ }
+}
+
// function to reset highlighting
export function resetHighlightedLines() {
firstHighlightedLine = null;
$('.hljs-ln-line[data-line-number]').css('background-color', 'inherit');
}
-export function scrollToLine(lineDomElt) {
+export function scrollToLine(lineDomElt, offset = 70) {
if ($(lineDomElt).closest('.swh-content').length > 0) {
$('html, body').animate({
- scrollTop: $(lineDomElt).offset().top - 70
+ scrollTop: $(lineDomElt).offset().top - offset
}, 500);
}
}
-export async function highlightCode(showLineNumbers = true, selector = 'code') {
+export async function highlightCode(showLineNumbers = true, selector = 'code',
+ enableLinesSelection = true) {
await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs');
@@ -60,9 +74,7 @@
} else if (lines[0] < lines[lines.length - 1]) {
firstHighlightedLine = parseInt(lines[0]);
scrollToLine(highlightLine(lines[0]));
- for (let i = lines[0] + 1; i <= lines[lines.length - 1]; ++i) {
- highlightLine(i);
- }
+ highlightLines(lines[0] + 1, lines[lines.length - 1]);
}
}
@@ -75,7 +87,7 @@
}
});
- if (!showLineNumbers) {
+ if (!showLineNumbers || !enableLinesSelection) {
return;
}
@@ -88,9 +100,7 @@
if (evt.shiftKey && firstHighlightedLine && line > firstHighlightedLine) {
const firstLine = firstHighlightedLine;
resetHighlightedLines();
- for (let i = firstLine; i <= line; ++i) {
- highlightLine(i);
- }
+ highlightLines(firstLine, line);
firstHighlightedLine = firstLine;
window.location.hash = `#L${firstLine}-L${line}`;
} else {
diff --git a/assets/src/bundles/webapp/iframes.js b/assets/src/bundles/webapp/iframes.js
new file mode 100644
--- /dev/null
+++ b/assets/src/bundles/webapp/iframes.js
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2021 The Software Heritage developers
+ * See the AUTHORS file at the top-level directory of this distribution
+ * License: GNU Affero General Public License version 3, or any later version
+ * See top-level LICENSE file for more information
+ */
+
+export function showIframeInfoModal(objectType, objectSWHID) {
+ const html = `
+ <p>
+ You can embed that ${objectType} view in an external website
+ through the use of an iframe. Use the following HTML code
+ to do so.
+ </p>
+ <pre><code class="swh-iframe-html html"><iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+ src="${window.location.origin}${Urls.swhid_iframe(objectSWHID.replaceAll('\n', ''))}">
+</iframe></code></pre>
+ <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+ src="${window.location.origin}${Urls.swhid_iframe(objectSWHID.replaceAll('\n', ''))}">
+ </iframe>`;
+ swh.webapp.showModalHtml(`Software Heritage ${objectType} iframe`, html, '1000px');
+ swh.webapp.highlightCode(false, '.swh-iframe-html');
+}
diff --git a/assets/src/bundles/webapp/index.js b/assets/src/bundles/webapp/index.js
--- a/assets/src/bundles/webapp/index.js
+++ b/assets/src/bundles/webapp/index.js
@@ -26,3 +26,4 @@
export * from './sentry';
export * from './math-typesetting';
export * from './status-widget';
+export * from './iframes';
diff --git a/assets/src/bundles/webapp/webapp-utils.js b/assets/src/bundles/webapp/webapp-utils.js
--- a/assets/src/bundles/webapp/webapp-utils.js
+++ b/assets/src/bundles/webapp/webapp-utils.js
@@ -251,9 +251,11 @@
$('#swh-web-modal-confirm').modal('show');
}
-export function showModalHtml(title, html) {
+export function showModalHtml(title, html, width = '500px') {
$('#swh-web-modal-html .modal-title').text(title);
$('#swh-web-modal-html .modal-body').html(html);
+ $('#swh-web-modal-html .modal-dialog').css('max-width', width);
+ $('#swh-web-modal-html .modal-dialog').css('width', width);
$('#swh-web-modal-html').modal('show');
}
diff --git a/assets/src/bundles/webapp/webapp.css b/assets/src/bundles/webapp/webapp.css
--- a/assets/src/bundles/webapp/webapp.css
+++ b/assets/src/bundles/webapp/webapp.css
@@ -662,6 +662,7 @@
}
.swh-badge-html,
+.swh-iframe-html,
.swh-badge-md,
.swh-badge-rst {
white-space: pre-wrap !important;
diff --git a/docs/index.rst b/docs/index.rst
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -15,6 +15,7 @@
uri-scheme-api
uri-scheme-browse
uri-scheme-identifiers
+ uri-scheme-misc
diff --git a/docs/uri-scheme-misc.rst b/docs/uri-scheme-misc.rst
new file mode 100644
--- /dev/null
+++ b/docs/uri-scheme-misc.rst
@@ -0,0 +1,41 @@
+Miscellaneous URLs
+^^^^^^^^^^^^^^^^^^
+
+Iframe view for contents and directories
+----------------------------------------
+
+A subset of Software Heritage objects (contents and directories) can be embedded
+in external websites through the use of iframes. A dedicated endpoint serving
+a minimalist Web UI is available for that use case.
+
+.. http:get:: /embed/(swhid)/
+
+ Endpoint to embed Software Heritage objects in external websites using
+ an iframe.
+
+ :param string swhid: a SoftWare Heritage persistent IDentifier
+ object, or SWHID (see :ref:`persistent-identifiers` to learn more about its syntax)
+
+ :statuscode 200: no error
+ :statuscode 400: the provided identifier is malformed or
+ the object type is not supported by the view
+ :statuscode 404: requested object cannot be found in the archive
+
+ **Example:**
+
+ By adding HTML code similar to the one below in a web page,
+
+ .. code-block:: html
+
+ <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+ src="https://archive.softwareheritage.org/embed/swh:1:cnt:edc043a59197bcebc1d44fb70bf1b84cde3db791;origin=https://github.com/rdicosmo/parmap;visit=swh:1:snp:2d869aa00591d2ac8ec8e7abacdda563d413189d;anchor=swh:1:rev:f140dbc8b05aa3d341c70436a1920a06df9a0ed4;path=/src/parmap.ml">
+ </iframe>
+
+ you will obtain the following rendering.
+
+ .. raw:: html
+
+ <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+ src="https://archive.softwareheritage.org/embed/swh:1:cnt:edc043a59197bcebc1d44fb70bf1b84cde3db791;origin=https://github.com/rdicosmo/parmap;visit=swh:1:snp:2d869aa00591d2ac8ec8e7abacdda563d413189d;anchor=swh:1:rev:f140dbc8b05aa3d341c70436a1920a06df9a0ed4;path=/src/parmap.ml">
+ </iframe>
+
diff --git a/swh/web/common/utils.py b/swh/web/common/utils.py
--- a/swh/web/common/utils.py
+++ b/swh/web/common/utils.py
@@ -285,6 +285,7 @@
),
"swh_web_version": get_distribution("swh.web").version,
"visit_types": ORIGIN_VISIT_TYPES,
+ "iframe_mode": False,
}
diff --git a/swh/web/misc/iframe.py b/swh/web/misc/iframe.py
new file mode 100644
--- /dev/null
+++ b/swh/web/misc/iframe.py
@@ -0,0 +1,310 @@
+# Copyright (C) 2021 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from typing import Any, Dict, List, Optional
+
+from django.conf.urls import url
+from django.shortcuts import render
+from django.views.decorators.clickjacking import xframe_options_exempt
+
+from swh.model.hashutil import hash_to_bytes
+from swh.model.identifiers import (
+ CONTENT,
+ DIRECTORY,
+ REVISION,
+ SNAPSHOT,
+ ObjectType,
+ QualifiedSWHID,
+)
+from swh.web.browse.snapshot_context import get_snapshot_context
+from swh.web.browse.utils import (
+ content_display_max_size,
+ get_directory_entries,
+ prepare_content_for_display,
+ request_content,
+)
+from swh.web.common import archive
+from swh.web.common.exc import BadInputExc, NotFoundExc, http_status_code_message
+from swh.web.common.identifiers import get_swhid, get_swhids_info
+from swh.web.common.typing import SnapshotContext, SWHObjectInfo
+from swh.web.common.utils import gen_path_info, reverse
+
+
+def _get_content_rendering_data(cnt_swhid: QualifiedSWHID, path: str) -> Dict[str, Any]:
+ content_data = request_content(f"sha1_git:{cnt_swhid.object_id.hex()}")
+ content = None
+ language = None
+ mimetype = None
+ if content_data.get("raw_data") is not None:
+ content_display_data = prepare_content_for_display(
+ content_data["raw_data"], content_data["mimetype"], path
+ )
+ content = content_display_data["content_data"]
+ language = content_display_data["language"]
+ mimetype = content_display_data["mimetype"]
+
+ return {
+ "content": content,
+ "content_size": content_data.get("length"),
+ "max_content_size": content_display_max_size,
+ "filename": path.split("/")[-1],
+ "encoding": content_data.get("encoding"),
+ "mimetype": mimetype,
+ "language": language,
+ }
+
+
+def _get_directory_rendering_data(
+ dir_swhid: QualifiedSWHID, focus_swhid: QualifiedSWHID, path: str,
+) -> Dict[str, Any]:
+ dirs, files = get_directory_entries(dir_swhid.object_id.hex())
+ for d in dirs:
+ if d["type"] == "rev":
+ d["url"] = None
+ else:
+ dir_swhid = QualifiedSWHID(
+ object_type=ObjectType.DIRECTORY,
+ object_id=hash_to_bytes(d["target"]),
+ origin=dir_swhid.origin,
+ visit=dir_swhid.visit,
+ anchor=dir_swhid.anchor,
+ path=(path or "/") + d["name"] + "/",
+ )
+ d["url"] = reverse(
+ "swhid-iframe",
+ url_args={"swhid": str(dir_swhid)},
+ query_params={"focus_swhid": str(focus_swhid)},
+ )
+
+ for f in files:
+ object_id = hash_to_bytes(f["target"])
+ cnt_swhid = QualifiedSWHID(
+ object_type=ObjectType.CONTENT,
+ object_id=object_id,
+ origin=dir_swhid.origin,
+ visit=dir_swhid.visit,
+ anchor=dir_swhid.anchor,
+ path=(path or "/") + f["name"],
+ lines=(focus_swhid.lines if object_id == focus_swhid.object_id else None),
+ )
+ f["url"] = reverse(
+ "swhid-iframe",
+ url_args={"swhid": str(cnt_swhid)},
+ query_params={"focus_swhid": str(focus_swhid)},
+ )
+
+ return {"dirs": dirs, "files": files}
+
+
+def _get_breacrumbs_data(
+ swhid: QualifiedSWHID,
+ focus_swhid: QualifiedSWHID,
+ path: str,
+ snapshot_context: Optional[SnapshotContext] = None,
+) -> List[Dict[str, Any]]:
+ breadcrumbs = []
+ filename = None
+ # strip any leading or trailing slash from path qualifier of SWHID
+ if path and path[0] == "/":
+ path = path[1:]
+ if path and path[-1] == "/":
+ path = path[:-1]
+ if swhid.object_type == ObjectType.CONTENT:
+ split_path = path.split("/")
+ filename = split_path[-1]
+ path = path[: -len(filename)]
+
+ path_info = gen_path_info(path) if path != "/" else []
+
+ root_dir = None
+ if snapshot_context and snapshot_context["root_directory"]:
+ root_dir = snapshot_context["root_directory"]
+ elif focus_swhid.object_type == ObjectType.DIRECTORY:
+ root_dir = focus_swhid.object_id.hex()
+
+ if root_dir:
+ root_dir_swhid = QualifiedSWHID(
+ object_type=ObjectType.DIRECTORY,
+ object_id=hash_to_bytes(root_dir),
+ origin=swhid.origin,
+ visit=swhid.visit,
+ anchor=swhid.anchor,
+ )
+ breadcrumbs.append(
+ {
+ "name": root_dir[:7],
+ "object_id": root_dir_swhid.object_id.hex(),
+ "path": "/",
+ "url": reverse(
+ "swhid-iframe",
+ url_args={"swhid": str(root_dir_swhid)},
+ query_params={"focus_swhid": focus_swhid},
+ ),
+ }
+ )
+
+ for pi in path_info:
+ dir_info = archive.lookup_directory_with_path(root_dir, pi["path"])
+ dir_swhid = QualifiedSWHID(
+ object_type=ObjectType.DIRECTORY,
+ object_id=hash_to_bytes(dir_info["target"]),
+ origin=swhid.origin,
+ visit=swhid.visit,
+ anchor=swhid.anchor,
+ path="/" + pi["path"] + "/",
+ )
+ breadcrumbs.append(
+ {
+ "name": pi["name"],
+ "object_id": dir_swhid.object_id.hex(),
+ "path": dir_swhid.path.decode("utf-8") if dir_swhid.path else "",
+ "url": reverse(
+ "swhid-iframe",
+ url_args={"swhid": str(dir_swhid)},
+ query_params={"focus_swhid": focus_swhid},
+ ),
+ }
+ )
+ if filename:
+ breadcrumbs.append(
+ {
+ "name": filename,
+ "object_id": swhid.object_id.hex(),
+ "path": path,
+ "url": "",
+ }
+ )
+
+ return breadcrumbs
+
+
+@xframe_options_exempt
+def swhid_iframe(request, swhid: str):
+ """Django view that can be embedded in an iframe to display objects archived
+ by Software Heritage (currently contents and directories) in a minimalist
+ Web UI.
+ """
+ focus_swhid = request.GET.get("focus_swhid", swhid)
+ parsed_swhid = None
+ view_data = {}
+ breadcrumbs = []
+ swh_objects = []
+ snapshot_context = None
+ swhids_info_extra_context = {}
+ try:
+ parsed_swhid = get_swhid(swhid)
+ parsed_focus_swhid = get_swhid(focus_swhid)
+ path = parsed_swhid.path.decode("utf-8") if parsed_swhid.path else ""
+
+ snapshot_context = None
+ revision_id = None
+ if (
+ parsed_swhid.anchor
+ and parsed_swhid.anchor.object_type == ObjectType.REVISION
+ ):
+ revision_id = parsed_swhid.anchor.object_id.hex()
+ if parsed_swhid.origin or parsed_swhid.visit:
+ snapshot_context = get_snapshot_context(
+ origin_url=parsed_swhid.origin,
+ snapshot_id=parsed_swhid.visit.object_id.hex()
+ if parsed_swhid.visit
+ else None,
+ revision_id=revision_id,
+ )
+
+ error_info: Dict[str, Any] = {"status_code": 200, "description": ""}
+
+ if parsed_swhid and parsed_swhid.object_type == ObjectType.CONTENT:
+ view_data = _get_content_rendering_data(parsed_swhid, path)
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=CONTENT, object_id=parsed_swhid.object_id.hex()
+ )
+ )
+
+ elif parsed_swhid and parsed_swhid.object_type == ObjectType.DIRECTORY:
+ view_data = _get_directory_rendering_data(
+ parsed_swhid, parsed_focus_swhid, path
+ )
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=DIRECTORY, object_id=parsed_swhid.object_id.hex()
+ )
+ )
+
+ elif parsed_swhid:
+ error_info = {
+ "status_code": 400,
+ "description": (
+ f"Objects of type {parsed_swhid.object_type} are not supported"
+ ),
+ }
+
+ swhids_info_extra_context["path"] = path
+ if parsed_swhid and view_data:
+ breadcrumbs = _get_breacrumbs_data(
+ parsed_swhid, parsed_focus_swhid, path, snapshot_context
+ )
+
+ if parsed_swhid.object_type == ObjectType.CONTENT and len(breadcrumbs) > 1:
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=DIRECTORY, object_id=breadcrumbs[-2]["object_id"]
+ )
+ )
+ swhids_info_extra_context["path"] = breadcrumbs[-2]["path"]
+ swhids_info_extra_context["filename"] = breadcrumbs[-1]["name"]
+
+ if snapshot_context:
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=REVISION,
+ object_id=snapshot_context["revision_id"] or "",
+ )
+ )
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=SNAPSHOT,
+ object_id=snapshot_context["snapshot_id"] or "",
+ )
+ )
+
+ except BadInputExc as e:
+ error_info = {"status_code": 400, "description": f"BadInputExc: {str(e)}"}
+ except NotFoundExc as e:
+ error_info = {"status_code": 404, "description": f"NotFoundExc: {str(e)}"}
+ except Exception as e:
+ error_info = {"status_code": 500, "description": str(e)}
+
+ return render(
+ request,
+ "misc/iframe.html",
+ {
+ **view_data,
+ "iframe_mode": True,
+ "object_type": parsed_swhid.object_type.value if parsed_swhid else None,
+ "lines": parsed_swhid.lines if parsed_swhid else None,
+ "breadcrumbs": breadcrumbs,
+ "swhid": swhid,
+ "focus_swhid": focus_swhid,
+ "error_code": error_info["status_code"],
+ "error_message": http_status_code_message.get(error_info["status_code"]),
+ "error_description": error_info["description"],
+ "snapshot_context": None,
+ "swhids_info": get_swhids_info(
+ swh_objects, snapshot_context, swhids_info_extra_context
+ ),
+ },
+ status=error_info["status_code"],
+ )
+
+
+urlpatterns = [
+ url(
+ r"^embed/(?P<swhid>swh:[0-9]+:[a-z]+:[0-9a-f]+.*)$",
+ swhid_iframe,
+ name="swhid-iframe",
+ ),
+]
diff --git a/swh/web/misc/urls.py b/swh/web/misc/urls.py
--- a/swh/web/misc/urls.py
+++ b/swh/web/misc/urls.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2019 The Software Heritage developers
+# Copyright (C) 2019-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
@@ -51,6 +51,7 @@
url(r"^stat_counters/", _stat_counters, name="stat-counters"),
url(r"^", include("swh.web.misc.badges")),
url(r"^metrics/prometheus/$", prometheus_metrics, name="metrics-prometheus"),
+ url(r"^", include("swh.web.misc.iframe")),
]
diff --git a/swh/web/templates/includes/breadcrumbs.html b/swh/web/templates/includes/breadcrumbs.html
--- a/swh/web/templates/includes/breadcrumbs.html
+++ b/swh/web/templates/includes/breadcrumbs.html
@@ -1,24 +1,23 @@
{% comment %}
-Copyright (C) 2017-2018 The Software Heritage developers
+Copyright (C) 2017-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
{% endcomment %}
{% if breadcrumbs %}
- {% if breadcrumbs|length > 1 or breadcrumbs.0.url %}
- <nav class="bread-crumbs swh-browse-bread-crumbs">
- <ul>
- {% for bc in breadcrumbs %}
- {% if bc.url %}
- <li class="swh-path"><a href="{{ bc.url | safe }}">{{ bc.name }}</a></li>
- <li>/</li>
- {% else %}
- <li>{{ bc.name }}</li>
- {% endif %}
- {% endfor %}
- </ul>
- </nav>
- {% endif %}
+ {% if iframe_mode or breadcrumbs|length > 1 or breadcrumbs.0.url %}
+ <nav class="bread-crumbs swh-browse-bread-crumbs">
+ <ul>
+ {% for bc in breadcrumbs %}
+ {% if bc.url %}
+ <li class="swh-path"><a href="{{ bc.url | safe }}">{{ bc.name }}</a></li>
+ <li>/</li>
+ {% else %}
+ <li>{{ bc.name }}</li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ </nav>
+ {% endif %}
{% endif %}
-
diff --git a/swh/web/templates/includes/content-display.html b/swh/web/templates/includes/content-display.html
--- a/swh/web/templates/includes/content-display.html
+++ b/swh/web/templates/includes/content-display.html
@@ -1,5 +1,5 @@
{% comment %}
-Copyright (C) 2017-2020 The Software Heritage developers
+Copyright (C) 2017-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
@@ -12,12 +12,14 @@
{% if snapshot_context and snapshot_context.is_empty %}
{% include "includes/empty-snapshot.html" %}
{% else %}
- <div class="card">
- {% if filename %}
- <div class="swh-content-filename card-header bg-gray-light swh-heading-color">
- {{ filename }}
- </div>
- {% endif %}
+ {% if not iframe_mode %}
+ <div class="card">
+ {% if filename %}
+ <div class="swh-content-filename card-header bg-gray-light swh-heading-color">
+ {{ filename }}
+ </div>
+ {% endif %}
+ {% endif %}
<div class="swh-content">
{% if content_size > max_content_size %}
Content is too large to be displayed (size is greater than {{ max_content_size|filesizeformat }}).
@@ -47,7 +49,9 @@
{% include "includes/http-error.html" %}
{% endif %}
</div>
- </div>
+ {% if not iframe_mode %}
+ </div>
+ {% endif %}
{% if content %}
<script>
@@ -59,7 +63,7 @@
let codeContainer = $('code');
let content = codeContainer.text();
- swh.webapp.highlightCode();
+ swh.webapp.highlightCode(true, 'code', !{{ iframe_mode|jsonify }});
function updateLanguage(language) {
codeContainer.text(content);
@@ -72,7 +76,7 @@
const newUrl = window.location.pathname + '?' + urlParams.toString() + window.location.hash;
window.history.replaceState('', document.title, newUrl);
- swh.webapp.highlightCode();
+ swh.webapp.highlightCode(true, 'code', !{{ iframe_mode|jsonify }});
}
{% endif %}
diff --git a/swh/web/templates/includes/directory-display.html b/swh/web/templates/includes/directory-display.html
--- a/swh/web/templates/includes/directory-display.html
+++ b/swh/web/templates/includes/directory-display.html
@@ -1,11 +1,13 @@
{% comment %}
-Copyright (C) 2017-2020 The Software Heritage developers
+Copyright (C) 2017-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
{% endcomment %}
-{% include "includes/revision-info.html" %}
+{% if not iframe_mode %}
+ {% include "includes/revision-info.html" %}
+{% endif %}
{% if snapshot_context and snapshot_context.is_empty %}
{% include "includes/empty-snapshot.html" %}
@@ -53,7 +55,9 @@
</tbody>
</table>
</div>
- <hr class="mt-0 mb-2">
+ {% if not iframe_mode %}
+ <hr class="mt-0 mb-2">
+ {% endif %}
{% elif "revision_found" in swh_object_metadata and swh_object_metadata.revision_found is False %}
<i>Revision {{ swh_object_metadata.revision }} could not be found in the archive.</i>
<br/>
diff --git a/swh/web/templates/includes/http-error.html b/swh/web/templates/includes/http-error.html
--- a/swh/web/templates/includes/http-error.html
+++ b/swh/web/templates/includes/http-error.html
@@ -1,5 +1,5 @@
{% comment %}
-Copyright (C) 2018 The Software Heritage developers
+Copyright (C) 2018-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
@@ -18,16 +18,18 @@
<div class="swh-http-error-desc">
<pre>{{ error_description }}</pre>
- <div>
- <a class="btn" onclick="window.history.back();">
- <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
- Go back to previous page
- </a>
- or
- <a class="btn" href="{% url 'swh-web-homepage' %}">
- <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
- Go back to homepage
- </a>
- </div>
+ {% if not iframe_mode %}
+ <div>
+ <a class="btn" onclick="window.history.back();">
+ <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
+ Go back to previous page
+ </a>
+ or
+ <a class="btn" href="{% url 'swh-web-homepage' %}">
+ <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
+ Go back to homepage
+ </a>
+ </div>
+ {% endif %}
</div>
</div>
\ No newline at end of file
diff --git a/swh/web/templates/includes/show-swhids.html b/swh/web/templates/includes/show-swhids.html
--- a/swh/web/templates/includes/show-swhids.html
+++ b/swh/web/templates/includes/show-swhids.html
@@ -18,7 +18,8 @@
<div id="swh-identifiers-content">
<p>
To reference or cite the objects present in the Software Heritage archive, permalinks based on
- <a href="https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html">
+ <a target="_blank" rel="noopener noreferrer"
+ href="https://docs.softwahreheritage.org/devel/swh-model/persistent-identifiers.html">
SoftWare Heritage persistent IDentifiers (SWHIDs)
</a>
must be used instead of copying and pasting the url from the address bar of the browser (as there is no guarantee the current URI
@@ -55,22 +56,31 @@
{% endif %}
<div class="card">
<div class="card-body swhid-ui">
- <div class="swh-badges">
- {% if snapshot_context and snapshot_context.origin_info %}
- <img class="swh-badge swh-badge-origin"
- src="{% url 'swh-badge' 'origin' snapshot_context.origin_info.url %}"
- onclick="swh.webapp.showBadgeInfoModal('origin', '{{ snapshot_context.origin_info.url|urlencode:"/?:@&" }}')"
- title="Click to display badge integration info">
- {% endif %}
- {% if swhid_info.object_id %}
- <img class="swh-badge swh-badge-{{ swhid_info.object_type }}"
- src="{% url 'swh-badge' swhid_info.object_type swhid_info.object_id %}"
- onclick="swh.webapp.showBadgeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())"
- title="Click to display badge integration info">
- {% endif %}
- </div>
+ {% if not iframe_mode %}
+ <div class="swh-badges">
+ {% if snapshot_context and snapshot_context.origin_info %}
+ <img class="swh-badge swh-badge-origin"
+ src="{% url 'swh-badge' 'origin' snapshot_context.origin_info.url %}"
+ onclick="swh.webapp.showBadgeInfoModal('origin', '{{ snapshot_context.origin_info.url|urlencode:"/?:@&" }}')"
+ title="Click to display badge integration info">
+ {% endif %}
+ {% if swhid_info.object_id %}
+ <img class="swh-badge swh-badge-{{ swhid_info.object_type }}"
+ src="{% url 'swh-badge' swhid_info.object_type swhid_info.object_id %}"
+ onclick="swh.webapp.showBadgeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())"
+ title="Click to display badge integration info">
+ {% endif %}
+ {% if swhid_info.object_type == "content" or swhid_info.object_type == "directory" %}
+ <a class="float-right" style="cursor: pointer;"
+ onclick="swh.webapp.showIframeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())">
+ Iframe embedding
+ </a>
+ {% endif %}
+ </div>
+
+ {% endif %}
{% if swhid_info.object_id %}
- <pre><a class="swhid" id="{{ swhid_info.swhid }}" href="{{ swhid_info.swhid_url }}">{{ swhid_info.swhid }}</a></pre>
+ <pre><a class="swhid" target="_blank" rel="noopener noreferrer" id="{{ swhid_info.swhid }}" href="{{ swhid_info.swhid_url }}">{{ swhid_info.swhid }}</a></pre>
{% endif %}
{% if swhid_info.swhid_with_context is not None %}
<div class="float-left">
diff --git a/swh/web/templates/misc/iframe.html b/swh/web/templates/misc/iframe.html
new file mode 100644
--- /dev/null
+++ b/swh/web/templates/misc/iframe.html
@@ -0,0 +1,182 @@
+{% comment %}
+Copyright (C) 2021 The Software Heritage developers
+See the AUTHORS file at the top-level directory of this distribution
+License: GNU Affero General Public License version 3, or any later version
+See top-level LICENSE file for more information
+{% endcomment %}
+
+<!DOCTYPE html>
+
+{% load static %}
+{% load render_bundle from webpack_loader %}
+{% load swh_templatetags %}
+
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <title>Software Heritage archived object</title>
+ <script src="{% url 'js_reverse' %}" type="text/javascript"></script>
+ {% render_bundle 'vendors' %}
+ {% render_bundle 'webapp' %}
+ {% render_bundle 'browse' %}
+ <script>
+/*
+@licstart The following is the entire license notice for the JavaScript code in this page.
+
+Copyright (C) 2021 The Software Heritage developers
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+@licend The above is the entire license notice for the JavaScript code in this page.
+*/
+ </script>
+ <style>
+ .card {
+ border: none;
+ margin-bottom: 0 !important;
+ }
+
+ .card-header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 80px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+ }
+
+ .breadcrumbs-container {
+ border-top: 1px solid rgba(0, 0, 0, 0.125);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+ background: white;
+ margin-left: -20px;
+ margin-right: -20px;
+ margin-bottom: 5px;
+ }
+
+ .breadcrumbs-container ul {
+ padding-left: 25px;
+ }
+
+ .card-block {
+ margin-top: 78px;
+ overflow: auto;
+ }
+
+ .swh-content pre {
+ margin-top: 5px;
+ margin-bottom: 0;
+ padding: 0;
+ }
+
+ .swh-directory-table {
+ margin-top: 3px;
+ }
+
+ .bread-crumbs {
+ font-size: large;
+ }
+
+ .hljs-ln-numbers {
+ cursor: default !important;
+ }
+
+ #swh-identifiers {
+ top: 60px !important;
+ }
+
+ #swh-identifiers .card-body {
+ padding-top: 0;
+ padding-bottom: 0;
+ padding-left: 8px;
+ }
+
+ </style>
+ </head>
+
+ <body style="padding-bottom: 0;">
+ <div class="wrapper" style="margin-left: 0;">
+ <div class="content">
+ <div class="container-fluid">
+ {% include "includes/show-swhids.html" %}
+ <div class="card">
+ <div class="card-header bg-gray-light">
+ <div class="d-flex align-items-center">
+ <a class="mr-auto" href="https://www.softwareheritage.org"
+ target="_blank" rel="noopener noreferrer">
+ <div class="brand-text sitename">
+ <img src="{% static 'img/swh-logo.svg' %}" style="height: 40px;"/>
+ <span class="first-word pl-1">Software</span>
+ <span class="second-word">Heritage</span>
+ </div>
+ </a>
+ <div class="d-none d-md-block">
+ Navigating in
+ <img src="{% url 'swh-badge-swhid' swhid %}">
+ </div>
+ <div class="ml-auto d-flex align-items-center">
+ {% if swhid != focus_swhid %}
+ <a class="d-flex align-items-center pr-2" href="{% url 'swhid-iframe' focus_swhid %}"
+ title="Reset view to its original state">
+ <div class="d-none d-lg-block">Reset view</div>
+ <i class="mdi mdi-refresh" aria-hidden="true"></i>
+ </a>
+ {% endif %}
+ <a class="d-none d-lg-block" href="{% url 'browse-swhid' swhid %}"
+ target="_blank" rel="noopener noreferrer">
+ View in the archive
+ <i class="mdi mdi-open-in-new" aria-hidden="true"></i>
+ </a>
+ <a class="d-lg-none" href="{% url 'browse-swhid' swhid %}"
+ target="_blank" rel="noopener noreferrer"
+ title="Go to archive">
+ <i class="mdi mdi-open-in-new" aria-hidden="true"></i>
+ </a>
+ </div>
+ </div>
+ <div class="d-flex align-items-center breadcrumbs-container mt-1">
+ {% include "includes/breadcrumbs.html" %}
+ </div>
+ </div>
+ <div class="card-block">
+ {% if error_code != 200 %}
+ {% include "includes/http-error.html" %}
+ {% elif object_type == "cnt" %}
+ {% include "includes/content-display.html" %}
+ {% elif object_type == "dir" %}
+ {% include "includes/directory-display.html" %}
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <a href="{% url 'jslicenses' %}" rel="jslicense" style="display: none;">
+ JavaScript license information
+ </a>
+ {% if object_type == "cnt" %}
+ <script>
+ setTimeout(function() {
+ {% if lines %}
+ swh.webapp.scrollToLine(swh.webapp.highlightLine({{ lines.0|jsonify }}), 100);
+ swh.webapp.highlightLines({{ lines.0|jsonify }}, {{ lines.1|jsonify }});
+ {% endif %}
+ }, 500);
+ </script>
+ {% endif %}
+ </body>
+
+</html>
diff --git a/swh/web/tests/data.py b/swh/web/tests/data.py
--- a/swh/web/tests/data.py
+++ b/swh/web/tests/data.py
@@ -20,6 +20,7 @@
from swh.indexer.storage.model import OriginIntrinsicMetadataRow
from swh.loader.git.from_disk import GitLoaderFromArchive
from swh.model.hashutil import DEFAULT_ALGORITHMS, hash_to_hex
+from swh.model.identifiers import CoreSWHID, ObjectType, QualifiedSWHID
from swh.model.model import (
Content,
Directory,
@@ -277,18 +278,35 @@
revisions = set()
releases = set()
snapshots = set()
+ swhids = []
content_path = {}
# Get all objects loaded into the test archive
common_metadata = {ORIGIN_METADATA_KEY: ORIGIN_METADATA_VALUE}
for origin in _TEST_ORIGINS:
+ origin_revisions = set()
snp = snapshot_get_latest(storage, origin["url"])
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id, origin=origin["url"]
+ )
+ )
snapshots.add(hash_to_hex(snp.id))
for branch_name, branch_data in snp.branches.items():
target_type = branch_data.target_type.value
if target_type == "revision":
- revisions.add(branch_data.target)
+ origin_revisions.add(branch_data.target)
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.REVISION,
+ object_id=branch_data.target,
+ origin=origin["url"],
+ visit=CoreSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id
+ ),
+ )
+ )
if b"master" in branch_name:
# Add some origin intrinsic metadata for tests
metadata = common_metadata
@@ -310,14 +328,24 @@
)
elif target_type == "release":
release = storage.release_get([branch_data.target])[0]
- revisions.add(release.target)
+ origin_revisions.add(release.target)
releases.add(hash_to_hex(branch_data.target))
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.RELEASE,
+ object_id=branch_data.target,
+ origin=origin["url"],
+ visit=CoreSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id
+ ),
+ )
+ )
- for rev_log in storage.revision_shortlog(set(revisions)):
+ for rev_log in storage.revision_shortlog(origin_revisions):
rev_id = rev_log[0]
revisions.add(rev_id)
- for rev in storage.revision_get(revisions):
+ for rev in storage.revision_get(origin_revisions):
if rev is None:
continue
dir_id = rev.directory
@@ -328,8 +356,36 @@
content_path[entry["sha1"]] = "/".join(
[hash_to_hex(dir_id), entry["path"].decode("utf-8")]
)
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.CONTENT,
+ object_id=entry["sha1_git"],
+ origin=origin["url"],
+ visit=CoreSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id
+ ),
+ anchor=CoreSWHID(
+ object_type=ObjectType.REVISION, object_id=rev.id
+ ),
+ path=b"/" + entry["path"],
+ )
+ )
elif entry["type"] == "dir":
directories.add(hash_to_hex(entry["target"]))
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.DIRECTORY,
+ object_id=entry["target"],
+ origin=origin["url"],
+ visit=CoreSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id
+ ),
+ anchor=CoreSWHID(
+ object_type=ObjectType.REVISION, object_id=rev.id
+ ),
+ path=b"/" + entry["path"] + b"/",
+ )
+ )
_add_extra_contents(storage, sha1s)
@@ -413,7 +469,7 @@
"releases": list(releases),
"revisions": list(map(hash_to_hex, revisions)),
"snapshots": list(snapshots),
- "generated_checksums": set(),
+ "swhids": swhids,
}
diff --git a/swh/web/tests/misc/test_iframe.py b/swh/web/tests/misc/test_iframe.py
new file mode 100644
--- /dev/null
+++ b/swh/web/tests/misc/test_iframe.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2021 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from hypothesis import given
+
+from swh.model.hashutil import hash_to_bytes
+from swh.model.identifiers import CoreSWHID, ObjectType
+from swh.web.common.utils import reverse
+from swh.web.tests.strategies import (
+ content_swhid,
+ directory_swhid,
+ revision_swhid,
+ unknown_directory,
+)
+from swh.web.tests.utils import check_html_get_response
+
+
+@given(content_swhid())
+def test_content_swhid_iframe(client, content_swhid):
+ url = reverse("swhid-iframe", url_args={"swhid": str(content_swhid)})
+ check_html_get_response(
+ client, url, status_code=200, template_used="misc/iframe.html"
+ )
+
+
+@given(content_swhid())
+def test_content_core_swhid_iframe(client, content_swhid):
+ content_core_swhid = CoreSWHID(
+ object_type=content_swhid.object_type, object_id=content_swhid.object_id
+ )
+ url = reverse("swhid-iframe", url_args={"swhid": str(content_core_swhid)})
+ check_html_get_response(
+ client, url, status_code=200, template_used="misc/iframe.html"
+ )
+
+
+@given(directory_swhid())
+def test_directory_swhid_iframe(client, directory_swhid):
+ url = reverse("swhid-iframe", url_args={"swhid": str(directory_swhid)})
+ check_html_get_response(
+ client, url, status_code=200, template_used="misc/iframe.html"
+ )
+
+
+@given(directory_swhid())
+def test_directory_core_swhid_iframe(client, directory_swhid):
+ directory_core_swhid = CoreSWHID(
+ object_type=directory_swhid.object_type, object_id=directory_swhid.object_id
+ )
+ url = reverse("swhid-iframe", url_args={"swhid": str(directory_core_swhid)})
+ check_html_get_response(
+ client, url, status_code=200, template_used="misc/iframe.html"
+ )
+
+
+@given(revision_swhid())
+def test_iframe_unsupported_object(client, revision_swhid):
+ url = reverse("swhid-iframe", url_args={"swhid": str(revision_swhid)})
+ check_html_get_response(
+ client, url, status_code=400, template_used="misc/iframe.html"
+ )
+
+
+@given(unknown_directory())
+def test_iframe_object_not_found(client, unknown_directory):
+ swhid = CoreSWHID(
+ object_type=ObjectType.DIRECTORY, object_id=hash_to_bytes(unknown_directory)
+ )
+ url = reverse("swhid-iframe", url_args={"swhid": str(swhid)})
+ check_html_get_response(
+ client, url, status_code=404, template_used="misc/iframe.html"
+ )
+
+
+@given(content_swhid())
+def test_swhid_iframe_unknown_error(client, mocker, content_swhid):
+ mocker.patch("swh.web.misc.iframe.get_swhid").side_effect = Exception("Error")
+ url = reverse("swhid-iframe", url_args={"swhid": str(content_swhid)})
+ check_html_get_response(
+ client, url, status_code=500, template_used="misc/iframe.html"
+ )
diff --git a/swh/web/tests/strategies.py b/swh/web/tests/strategies.py
--- a/swh/web/tests/strategies.py
+++ b/swh/web/tests/strategies.py
@@ -23,6 +23,7 @@
from swh.model.hashutil import DEFAULT_ALGORITHMS, hash_to_bytes, hash_to_hex
from swh.model.hypothesis_strategies import origins as new_origin_strategy
from swh.model.hypothesis_strategies import snapshots as new_snapshot
+from swh.model.identifiers import ObjectType
from swh.model.model import (
Content,
Directory,
@@ -646,3 +647,51 @@
"rev_dir_rev_path": "libtess2",
}
)
+
+
+def swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for any object
+ ingested into the test archive.
+ """
+ return _known_swh_object("swhids")
+
+
+def content_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a content object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.CONTENT)
+
+
+def directory_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a directory object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.DIRECTORY)
+
+
+def release_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a release object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.RELEASE)
+
+
+def revision_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a revision object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.REVISION)
+
+
+def snapshot_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a snapshot object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.SNAPSHOT)
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Nov 5 2024, 8:00 AM (8 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3216579
Attached To
D6186: misc: Add iframe view for contents and directories
Event Timeline
Log In to Comment