Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F9346152
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
51 KB
Subscribers
None
View Options
diff --git a/swh/web/static/css/style.css b/swh/web/static/css/style.css
index 21335844b..0be109da7 100644
--- a/swh/web/static/css/style.css
+++ b/swh/web/static/css/style.css
@@ -1,495 +1,502 @@
/*
version: 0.1
date: 21/09/15
author: swh
email: swh
website: softwareheritage.org
version history: /style.css
*/
@import url(https://fonts.googleapis.com/css?family=Alegreya:400,400italic,700,700italic);
@import url(https://fonts.googleapis.com/css?family=Alegreya+Sans:400,400italic,500,500italic,700,700italic,100,300,100italic,300italic);
html {
height: 100%;
}
body {
font-family: 'Alegreya Sans', sans-serif;
font-size: 1.7rem;
line-height: 1.5;
color: rgba(0, 0, 0, 0.55);
- padding-top: 80px; /* avoid fixed bootstrap navbar covers content */
padding-bottom: 120px;
min-height: 100%;
margin: 0;
position: relative;
}
.heading {
font-family: 'Alegreya', serif;
}
.shell, .text {
font-size: 0.7em;
}
.logo img {
max-height: 40px;
}
.logo .navbar-brand {
padding: 5px;
}
.logo .sitename {
padding: 15px 5px;
}
.jumbotron {
padding: 0;
background-color: rgba(0, 0, 0, 0);
position: fixed;
top: 0;
width: 100%;
z-index: 10;
}
#swh-navbar-collapse {
border-top-style: none;
border-left-style: none;
border-right-style: none;
border-bottom: 5px solid;
border-image: linear-gradient(to right, rgb(226, 0, 38) 0%, rgb(254, 205, 27) 100%) 1 1 1 1;
width: 100%;
padding: 5px;
}
.nav-horizontal {
float: right;
}
h3[id], h4[id], a[id] { /* avoid in-page links covered by navbar */
padding-top: 80px;
margin-top: -70px;
}
h1, h2, h3, h4 {
margin: 0;
color: #e20026;
padding-bottom: 10px;
}
h1 { font-size: 1.8em; }
h2 { font-size: 1.2em; }
h3 { font-size: 1.1em; }
a {
color: rgba(0, 0, 0, 0.75);
border-bottom-style: dotted;
border-bottom-width: 1px;
border-bottom-color: rgb(91, 94, 111);
}
a:hover {
color: black;
}
ul.dropdown-menu a,
.navbar-header a,
ul.navbar-nav a { /* No decoration on links in dropdown menu */
border-bottom-style: none;
color: #323232;
font-weight: 700;
}
.navbar-header a:hover,
ul.navbar-nav a:hover {
color: #8f8f8f;
}
.sitename .first-word, .sitename .second-word {
color: rgba(0, 0, 0, 0.75);
font-weight: normal;
font-size: 1.8rem;
}
.sitename .first-word {
font-family: 'Alegreya Sans', sans-serif;
}
.sitename .second-word {
font-family: 'Alegreya', serif;
}
ul.dropdown-menu > li,
ul.dropdown-menu > li > ul > li { /* No decoration on bullet points in dropdown menu */
list-style-type: none;
}
.page {
margin: 2em auto;
width: 35em;
border: 5px solid #ccc;
padding: 0.8em;
background: white;
}
.entries {
list-style: none;
margin: 0;
padding: 0;
}
.entries li {
margin: 0.8em 1.2em;
}
.entries li h2 {
margin-left: -1em;
}
.add-entry {
font-size: 0.9em;
border-bottom: 1px solid #ccc;
}
.add-entry dl {
font-weight: bold;
}
.metanav {
text-align: right;
font-size: 0.8em;
padding: 0.3em;
margin-bottom: 1em;
background: #fafafa;
}
.flash {
background: #cee5F5;
padding: 0.5em;
border: 1px solid #aacbe2;
}
.error {
background: #f0d6d6;
padding: 0.5em;
}
.file-found {
color: #23BA49;
}
.file-notfound {
color: #FF4747;
}
/* Bootstrap custom styling to correctly render multiple
* form-controls in an input-group:
* github.com/twbs/bootstrap/issues/12732 */
.input-group-field {
display: table-cell;
vertical-align: middle;
border-radius:4px;
min-width:1%;
white-space: nowrap;
}
.input-group-field .form-control {
border-radius: inherit !important;
}
.input-group-field:not(:first-child):not(:last-child) {
border-radius:0;
}
.input-group-field:not(:first-child):not(:last-child) .form-control {
border-left-width: 0;
border-right-width: 0;
}
.input-group-field:last-child {
border-top-left-radius:0;
border-bottom-left-radius:0;
}
.input-group > span:not(:last-child) > button {
border-radius: 0;
}
.multi-input-group > .input-group-btn {
vertical-align: bottom;
padding: 0;
}
.dataTables_filter {
margin-top: 15px;
}
.dataTables_filter input {
width: 70%;
float: right;
}
tr.api-doc-route-upcoming > td, tr.api-doc-route-upcoming > td > a {
font-size: 90%;
}
tr.api-doc-route-deprecated > td, tr.api-doc-route-deprecated > td > a {
color: red;
}
#back-to-top {
display: initial;
position: fixed;
bottom: 30px;
right: 30px;
z-index: 10;
}
#back-to-top a img {
display: block;
width: 32px;
height: 32px;
background-size: 32px 32px;
text-indent: -999px;
overflow: hidden;
}
.table > thead > tr > th {
border-bottom: 1px solid #e20026;
}
.table > tbody > tr > td {
border-style: none;
}
pre {
background-color: #f5f5f5;
}
.dataTables_wrapper {
position: static;
}
/* breadcrumbs */
.bread-crumbs{
display: inline-block;
overflow: hidden;
color: rgba(0, 0, 0, 0.55);
}
bread-crumbs ul {
list-style-type: none;
}
.bread-crumbs li {
float: left;
list-style-type: none;
}
.bread-crumbs a {
color: rgba(0, 0, 0, 0.75);
border-bottom-style: none;
}
.bread-crumbs a:hover {
color: rgba(0, 0, 0, 0.85);
text-decoration: underline;
}
.title-small .bread-crumbs{
margin: -30px 0 25px;
}
#footer {
background-color: #262626;
color: hsl(0, 0%, 100%);
font-size: 1.2rem;
text-align: center;
padding-top: 20px;
padding-bottom: 20px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
#footer a,
#footer a:visited {
color: hsl(0, 0%, 100%);
}
#footer a:hover {
text-decoration: underline;
}
.highlightjs pre {
background-color: transparent;
border-radius: 0px;
border-color: transparent;
}
.hljs {
background-color: transparent;
white-space: pre;
}
.scrollable-menu {
max-height: 180px;
overflow-x: hidden;
}
.swh-browse-top-navigation {
border-bottom: 1px solid #ddd;
min-height: 42px;
padding: 4px 5px 0px 5px;
}
.swh-browse-bread-crumbs {
font-size: inherit;
vertical-align: text-top;
margin-bottom: 1px;
}
+.swh-browse-bread-crumbs li:nth-child(n+2)::before {
+ content: "";
+ display: inline-block;
+ margin: 0 2px;
+}
+
.swh-metadata-table-row {
border-top: 1px solid #ddd !important;
}
/* for block of numbers */
td.hljs-ln-numbers {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-align: center;
color: #ccc;
border-right: 1px solid #CCC;
vertical-align: top;
padding-right: 5px;
/* your custom style here */
}
/* for block of code */
td.hljs-ln-code {
padding-left: 10px;
}
.btn-swh {
color: #6C6C6C;
background-color: #EAEAEA;
border-color: #EAEAEA;
+ background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);
+ background-repeat: repeat-x;
}
.btn-swh:hover,
.btn-swh:focus,
.btn-swh:active,
.btn-swh.active,
.open .dropdown-toggle.btn-swh {
color: #6C6C6C;
background-color: #D4D4D4;
border-color: #EAEAEA;
}
.btn-swh:active,
.btn-swh.active,
.open .dropdown-toggle.btn-swh {
background-image: none;
}
.btn-swh.disabled,
.btn-swh[disabled],
fieldset[disabled] .btn-swh,
.btn-swh.disabled:hover,
.btn-swh[disabled]:hover,
fieldset[disabled] .btn-swh:hover,
.btn-swh.disabled:focus,
.btn-swh[disabled]:focus,
fieldset[disabled] .btn-swh:focus,
.btn-swh.disabled:active,
.btn-swh[disabled]:active,
fieldset[disabled] .btn-swh:active,
.btn-swh.disabled.active,
.btn-swh[disabled].active,
fieldset[disabled] .btn-swh.active {
background-color: #EAEAEA;
border-color: #EAEAEA;
}
.btn-swh .badge {
color: #EAEAEA;
background-color: #6C6C6C;
}
.swh-http-error {
margin: 0 auto;
text-align: center;
}
.swh-http-error-head {
color: #2d353c;
font-size: 30px;
}
.swh-http-error-code {
bottom: 60%;
color: #2d353c;
font-size: 96px;
line-height: 80px;
margin-bottom: 10px!important;
}
.swh-http-error-desc {
font-size: 12px;
color: #647788;
}
.swh-http-error-desc pre {
text-align: left;
}
.swh-table {
border-bottom: none !important;
}
.swh-counter {
font-size: 150%;
}
.swh-loading {
display : none;
}
.swh-loading.show {
display:inline-block;
position: fixed;
background: white;
border: 1px solid black;
top: 50%;
left: 50%;
margin: -50px 0px 0px -50px;
text-align: center;
z-index:100;
}
.swh-readme a {
outline: none;
border: none;
}
.swh-readme table {
border-collapse: collapse;
}
.swh-readme table,
.swh-readme table th,
.swh-readme table td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
.swh-readme table tr:nth-child(even) {
background-color: #f2f2f2;
}
.swh-web-app-link:hover {
background-color: #efeff2;
}
.swh-web-app-link a {
text-decoration: none;
outline: none;
border: none;
}
\ No newline at end of file
diff --git a/swh/web/templates/content.html b/swh/web/templates/content.html
index 193871f13..ce138d998 100644
--- a/swh/web/templates/content.html
+++ b/swh/web/templates/content.html
@@ -1,126 +1,133 @@
{% extends "browse.html" %}
{% load static %}
{% block header %}
<script src="{% static 'js/highlightjs/dist/highlight.pack.js' %}"></script>
<script src="{% static 'js/highlightjs-line-numbers/dist/highlightjs-line-numbers.min.js' %}"></script>
<script>
// empty hljs language definition
function no_highlight(hljs) {
return {}
}
// just a trick to get line numbers working when no highlight
// has to be performed
hljs.registerLanguage('nohighlight-swh', no_highlight);
// keep track of the first highlighted line
var first_hl_line = null;
// highlighting color
var line_hl_color = 'rgb(193, 255, 193)';
// function to highlight a line
function highlight_line(i) {
var line_td = $('div[data-line-number="' + i + '"]').parent().parent();
line_td.css('background-color', line_hl_color);
return line_td;
}
+ function removeHash () {
+ history.pushState("", document.title, window.location.pathname + window.location.search);
+ }
+
// function to reset highlighting
function reset_highlighted_lines() {
first_hl_line = null;
$('tr').css('background-color', 'inherit');
}
function scrollToLine(lineDomElt) {
$('html, body').animate({
scrollTop: $(lineDomElt).offset().top - 70
}, 500);
}
// function to highlight lines based on a url fragment
// in the form '#Lx' or '#Lx-Ly'
function parse_url_fragment_for_lines_to_highlight() {
var lines = [];
var lines_regexp = new RegExp(/L(\d+)/g);
var line = lines_regexp.exec(window.location.hash);
while (line) {
lines.push(parseInt(line[1]));
line = lines_regexp.exec(window.location.hash);
}
reset_highlighted_lines();
if (lines.length == 1) {
first_hl_line = parseInt(lines[0]);
scrollToLine(highlight_line(lines[0]));
} else if (lines[0] < lines[lines.length - 1]) {
first_hl_line = parseInt(lines[0]);
scrollToLine(highlight_line(lines[0]));
for (var i = lines[0]+1; i <= lines[lines.length - 1]; ++i) {
highlight_line(i);
}
}
}
$(document).ready(function() {
// highlight code and add line numbers
$('code').each(function(i, block) {
hljs.highlightBlock(block);
hljs.lineNumbersBlock(block);
});
// click handler to dynamically highlight line(s)
// when the user clicks on a line number (lines range
// can also be highlighted while holding the shift key)
$('body').click(function(evt) {
if (evt.target.classList.contains('hljs-ln-n')) {
var line = parseInt($(evt.target).data('line-number'));
if (evt.shiftKey && first_hl_line && line > first_hl_line) {
var first_line = first_hl_line;
reset_highlighted_lines();
for (var i = first_line; i <= line; ++i) {
highlight_line(i);
}
first_hl_line = first_line;
window.location.hash = '#L' + first_line + "-L" + line;
} else {
reset_highlighted_lines();
highlight_line(line);
window.location.hash = '#L' + line;
scrollToLine(evt.target);
}
+ } else {
+ reset_highlighted_lines();
+ removeHash();
}
});
// update lines highlighting when the url fragment changes
$(window).on('hashchange', function() {
parse_url_fragment_for_lines_to_highlight();
});
// highlight lines specified by an url fragment
parse_url_fragment_for_lines_to_highlight();
});
</script>
<link rel="stylesheet" href="{% static 'css/highlightjs/github.css' %}">
{% endblock %}
{% block swh-browse-main-panel-content %}
{% include "includes/top-navigation.html" %}
<div class="well well-sm" style="margin-bottom: 0px">
{% if "inode/x-empty" == mimetype %}
<i>File is empty</i>
{% elif "text/" in mimetype %}
<div class="highlightjs">
<pre>
<code class="{{ language }}">{{ content }}</code>
</pre>
</div>
{% elif "image/" in mimetype and content %}
<img src="data:{{ mimetype }};base64,{{ content }}"/>
{% else %}
Content with mime type {{ mimetype }} can not be displayed
{% endif %}
</div>
{% endblock %}
diff --git a/swh/web/templates/includes/origins-search.html b/swh/web/templates/includes/origins-search.html
index b98acd96c..219b4a0da 100644
--- a/swh/web/templates/includes/origins-search.html
+++ b/swh/web/templates/includes/origins-search.html
@@ -1,166 +1,162 @@
{% load static %}
<div class="panel panel-default" style="overflow-x: auto;">
<div class="panel-heading">
<h2>Search Software Heritage origins to browse:</h2>
</div>
<div class="panel-body">
<form class="form-horizontal" id="search_origins">
<div class="input-group add-on">
<input class="form-control" placeholder="Enter string pattern(s) to search for in origin urls" type="text" id="origins-url-patterns"/>
<div class="input-group-btn">
<button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
</div>
</div>
</form>
<div class="swh-loading">
<img src="{% static 'img/swh-spinner.gif' %}"></img>
<p>Searching origins ...</p>
</div>
<div class="table-responsive">
<table class="table" id="origin-search-results">
<thead>
<tr>
- <th>Origin id</th>
<th>Origin type</th>
- <th>Origin url</th>
- <th></th>
+ <th>Origin browse url</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<ul class="pager">
<li class="disabled" id="origins-prev-results-button"><a href="#">Previous</a></li>
<li class="disabled" id="origins-next-results-button"><a href="#">Next</a></li>
</ul>
</div>
</div>
<script>
var origin_patterns;
var per_page = 15;
var offset = 0;
var origins_search_url = "{% url 'browse-origin-search' 'url_regexp' %}?limit=limit_val&offset=offset_val®exp=true";
var origin_browse_url = "{% url 'browse-origin' 'type' 'origin_url' %}";
var search_request = null;
function populateOriginSearchResultsTable(data, limit, offset) {
$("#origin-search-results tbody tr").remove();
var table = $("#origin-search-results tbody");
$.each(data, function(idx, elem){
var tableRow = '<tr>';
- tableRow += '<td>' + elem.id + '</td>';
tableRow += '<td>' + elem.type + '</td>';
- tableRow += '<td><a href="' + elem.url + '">' + elem.url + '</a></td>';
var browse_url = origin_browse_url.replace('type', elem.type).replace('origin_url', elem.url);
- tableRow += '<td><a href="' + browse_url + '">Browse</a></td>';
+ tableRow += '<td><a href="' + browse_url + '">' + browse_url + '</a></td>';
tableRow += '</tr>';
table.append(tableRow);
});
if (data.length == per_page) {
$('#origins-next-results-button').removeClass('disabled');
} else {
$('#origins-next-results-button').addClass('disabled');
}
if (offset > 0) {
$('#origins-prev-results-button').removeClass('disabled');
} else {
$('#origins-prev-results-button').addClass('disabled');
}
}
$(document).ready(function() {
if (typeof(Storage) !== "undefined") {
origin_patterns = sessionStorage.getItem("last-swh-origin-url-patterns");
var data = sessionStorage.getItem("last-swh-origin-search-results");
offset = sessionStorage.getItem("last-swh-origin-search-offset");
if (data) {
$("#origins-url-patterns").val(origin_patterns);
offset = parseInt(offset);
populateOriginSearchResultsTable(JSON.parse(data), per_page, offset);
}
}
});
// http://dsernst.com/2014/12/14/heaps-permutation-algorithm-in-javascript/
function swap(array, pos1, pos2) {
var temp = array[pos1];
array[pos1] = array[pos2];
array[pos2] = temp;
}
function heapsPermute(array, output, n) {
n = n || array.length; // set n default to array.length
if (n === 1) {
output(array);
} else {
for (var i = 1; i <= n; i += 1) {
heapsPermute(array, output, n - 1);
if (n % 2) {
var j = 1;
} else {
var j = i;
}
swap(array, j - 1, n - 1); // -1 to account for javascript zero-indexing
}
}
};
function searchOrigins(patterns, limit, offset) {
origin_patterns = patterns;
var patterns_array = patterns.trim().replace(/\s+/g,' ').split(' ');
var patterns_permut = []
- heapsPermute(patterns_array, (p) => {patterns_permut.push(p.join('.*'));})
+ heapsPermute(patterns_array, function(p) {patterns_permut.push(p.join('.*'));})
var regex = patterns_permut.join('|');
var search_url = origins_search_url.replace('url_regexp', regex)
.replace('limit_val', limit)
.replace('offset_val', offset);
if (search_request) {
search_request.abort();
}
$(".swh-loading").addClass('show');
search_request = $.ajax({
url: search_url,
dataType: 'json',
error: function() {
search_request = null;
$(".swh-loading").removeClass('show');
},
success: function (data) {
search_request = null;
if (typeof(Storage) !== "undefined") {
sessionStorage.setItem("last-swh-origin-url-patterns", patterns);
sessionStorage.setItem("last-swh-origin-search-results", JSON.stringify(data));
sessionStorage.setItem("last-swh-origin-search-offset", offset);
}
populateOriginSearchResultsTable(data, per_page, offset);
$(".swh-loading").removeClass('show');
}
});
}
$("#search_origins").submit(function (event) {
var patterns = $("#origins-url-patterns").val();
offset = 0;
searchOrigins(patterns, per_page, offset);
event.preventDefault();
});
$("#origins-next-results-button").click(function (event) {
offset += per_page;
searchOrigins(origin_patterns, per_page, offset);
event.preventDefault();
});
$("#origins-prev-results-button").click(function (event) {
offset -= per_page;
searchOrigins(origin_patterns, per_page, offset);
event.preventDefault();
});
</script>
\ No newline at end of file
diff --git a/swh/web/templates/layout.html b/swh/web/templates/layout.html
index 525f39633..645c499ab 100644
--- a/swh/web/templates/layout.html
+++ b/swh/web/templates/layout.html
@@ -1,81 +1,73 @@
<!DOCTYPE html>
{% load static %}
<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">
<title>{% block title %}{% endblock %}</title>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
{% block header %}{% endblock %}
<!-- BEGIN: bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}">
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.13/css/jquery.dataTables.min.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/1.10.13/js/jquery.dataTables.min.js"></script>
<!-- END: bootstrap -->
<link rel="stylesheet" href="{% static 'css/pygment.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}" />
<link rel="icon" href="{% static 'img/icons/swh-logo-32x32.png' %}" sizes="32x32" />
<link rel="icon" href="{% static 'img/icons/swh-logo-archive-192x192.png' %}" sizes="192x192" />
<link rel="apple-touch-icon-precomposed" href="{% static 'img/icons/swh-logo-archive-180x180.png' %}" />
<meta name="msapplication-TileImage" content="{% static 'img/icons/swh-logo-archive-270x270.png' %}" />
<script>document.domain = "softwareheritage.org";</script>
</head>
<body
<a id="top"></a>
- <div class="jumbotron">
- <nav class="navbar navbar-default" id="swh-navbar-collapse">
- <div class="navbar-header">
- <button type="button" class="navbar-toggle collapsed"
- data-toggle="collapse" data-target="#swh-navbar-collapse" aria-expanded="false"> <!-- screen-reader -->
- <span class="sr-only">Toggle navigation menu</span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- <div class="logo">
- <a class="navbar-brand" href="{% url 'swh-web-homepage' %}"> <!-- logo -->
- <img alt="SWH Archive" src="{% static 'img/swh-logo-archive.png' %}" class="swh-logo" />
- </a>
- <a class="navbar-brand sitename" href="{% url 'swh-web-homepage' %}">
- <span class="first-word">Software</span> <span class="second-word">Heritage</span>
- </a>
- </div>
+ <nav class="navbar navbar-default navbar-static-top" id="swh-navbar-collapse">
+ <div class="navbar-header">
+ <div class="logo">
+ <a class="navbar-brand" href="{% url 'swh-web-homepage' %}"> <!-- logo -->
+ <img alt="SWH Archive" src="{% static 'img/swh-logo-archive.png' %}" class="swh-logo" />
+ </a>
+ <a class="navbar-brand sitename" href="{% url 'swh-web-homepage' %}">
+ <span class="first-word">Software</span> <span class="second-word">Heritage</span>
+ </a>
</div>
- </nav>
- </div>
+ </div>
+ </nav>
+
<div class="container">
<div class="container">
</div>
<div class="container content">
{% block content %}{% endblock %}
</div>
</div>
<div id="footer" class="panel-footer">
<a href="https://www.softwareheritage.org">Software Heritage</a> —
Copyright (C) 2015–2017, The Software Heritage developers.
License: <a href="https://www.gnu.org/licenses/agpl.html">GNU
AGPLv3+</a>. <br /> The source code of Software Heritage <em>itself</em>
is available on
our <a href="https://forge.softwareheritage.org/">development
forge</a>. <br /> The source code files <em>archived</em> by Software
Heritage are available under their own copyright and licenses. <br />
<a href="https://www.softwareheritage.org/software-heritage-api-terms-of-use/">Terms of use</a> -
<a href="https://www.softwareheritage.org/contact/">Contact</a>.
</div>
<div id="back-to-top">
<a href="#top"><img alt="back to top" src="{% static 'img/arrow-up-small.png' %}" /></a>
</div>
</body>
</html>
diff --git a/swh/web/templates/origin.html b/swh/web/templates/origin.html
index 7931750ae..2b510978a 100644
--- a/swh/web/templates/origin.html
+++ b/swh/web/templates/origin.html
@@ -1,58 +1,56 @@
{% extends "browse.html" %}
{% load static %}
{% load swh_templatetags %}
{% block swh-browse-main-panel-content %}
<div class="panel-body">
<!-- Timeline result -->
<h2>Visits history</h2>
<h3>Calendar</h3>
<div class="timeline" style="margin-bottom: 20px">
<div id="swh-calendar-window" style="height: 200px">
<div id="cal-zoom-window" style="height: 60%"></div>
<div id="cal-static-window" style="height: 40%"></div>
</div>
<button class="btn btn-sm btn-swh pull-right" id="cal-clear">Reset zoom</button>
</div>
<h3>List</h3>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
- <th>Visit id</th>
<th>Visit date</th>
<th>Visit status</th>
<th>Tree</th>
</tr>
</thead>
<tbody>
{% for v in visits %}
<tr class="swh-origin-visit">
- <td>{{ v.visit }}</td>
<td>{{ v.fmt_date }}</td>
<td>{{ v.status }}</td>
<td><a href="{{ v.browse_url }}">{{ v.browse_url }}</a></td>
</tr>
{% endfor %}
<tbody>
</table>
</div>
</div>
<!-- Flot calendar setup -->
<!-- Flot plugin dependencies -->
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.time.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.selection.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/flot.tooltip/0.9.0/jquery.flot.tooltip.min.js"></script>
<script language="javascript" type="text/javascript" src="{% static 'js/calendar.js' %}"></script>
<script>
$(function(){
var cal = new Calendar('{{ browse_url_base }}', {{ origin_visits_data | safe }},
{{ swh_object_metadata.id }}, $('#cal-zoom-window'),
$('#cal-static-window'), $('#cal-clear'));
});
</script>
{% endblock %}
diff --git a/swh/web/tests/browse/views/test_origin.py b/swh/web/tests/browse/views/test_origin.py
index 6575cb49a..7b489e9e4 100644
--- a/swh/web/tests/browse/views/test_origin.py
+++ b/swh/web/tests/browse/views/test_origin.py
@@ -1,536 +1,535 @@
# Copyright (C) 2017 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
# flake8: noqa
from unittest.mock import patch
from nose.tools import istest, nottest
from django.test import TestCase
from django.utils.html import escape
from swh.web.common.exc import NotFoundExc
from swh.web.common.utils import (
reverse, gen_path_info, format_utc_iso_date,
parse_timestamp
)
from swh.web.tests.testbase import SWHWebTestBase
from .data.origin_test_data import (
origin_info_test_data,
origin_visits_test_data,
stub_content_origin_info, stub_content_origin_visit_id,
stub_content_origin_visit_unix_ts, stub_content_origin_visit_iso_date,
stub_content_origin_branch,
stub_content_origin_visits, stub_content_origin_branches,
stub_origin_info, stub_visit_id,
stub_origin_visits, stub_origin_branches,
stub_origin_root_directory_entries, stub_origin_master_branch,
stub_origin_root_directory_sha1, stub_origin_sub_directory_path,
stub_origin_sub_directory_entries, stub_visit_unix_ts, stub_visit_iso_date
)
from .data.content_test_data import (
stub_content_root_dir,
stub_content_text_data,
stub_content_text_path
)
class SwhBrowseOriginTest(SWHWebTestBase, TestCase):
@patch('swh.web.browse.views.origin.get_origin_visits')
@patch('swh.web.browse.views.origin.service')
@istest
def origin_browse(self, mock_service, mock_get_origin_visits):
mock_service.lookup_origin.return_value = origin_info_test_data
mock_get_origin_visits.return_value = origin_visits_test_data
url = reverse('browse-origin',
kwargs={'origin_type': origin_info_test_data['type'],
'origin_url': origin_info_test_data['url']})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 200)
self.assertTemplateUsed('origin.html')
- self.assertContains(resp, '<td>%s</td>' % origin_info_test_data['id'])
self.assertContains(resp, '<pre>%s</pre>' % origin_info_test_data['type'])
self.assertContains(resp, '<pre><a href="%s">%s</a></pre>' %
(origin_info_test_data['url'],
origin_info_test_data['url']))
self.assertContains(resp, '<tr class="swh-origin-visit">',
count=len(origin_visits_test_data))
for visit in origin_visits_test_data:
visit_date = format_utc_iso_date(visit['date'], '%Y-%m-%dT%H:%M:%S')
browse_url = reverse('browse-origin-directory',
kwargs={'origin_type': origin_info_test_data['type'],
'origin_url': origin_info_test_data['url'],
'timestamp': visit_date})
self.assertContains(resp, '<td><a href="%s">%s</a></td>' %
(browse_url, browse_url))
@nottest
def origin_content_view_test(self, origin_info, origin_visits,
origin_branches, origin_branch,
root_dir_sha1, content_sha1,
content_path, content_data,
content_language,
visit_id=None, timestamp=None):
url_args = {'origin_type': origin_info['type'],
'origin_url': origin_info['url'],
'path': content_path}
if not visit_id:
visit_id = origin_visits[-1]['visit']
query_params = {}
if timestamp:
url_args['timestamp'] = timestamp
else:
query_params['visit_id'] = visit_id
url = reverse('browse-origin-content',
kwargs=url_args,
query_params=query_params)
resp = self.client.get(url)
self.assertEquals(resp.status_code, 200)
self.assertTemplateUsed('content.html')
self.assertContains(resp, '<code class="%s">' % content_language)
self.assertContains(resp, escape(content_data))
split_path = content_path.split('/')
filename = split_path[-1]
path = content_path.replace(filename, '')[:-1]
path_info = gen_path_info(path)
del url_args['path']
query_params['branch'] = origin_branch
if timestamp:
url_args['timestamp'] = \
format_utc_iso_date(parse_timestamp(timestamp).isoformat(),
'%Y-%m-%dT%H:%M:%S')
root_dir_url = reverse('browse-origin-directory',
kwargs=url_args,
query_params=query_params)
self.assertContains(resp, '<li class="swh-path">',
count=len(path_info)+1)
self.assertContains(resp, '<a href="%s">%s</a>' %
(root_dir_url, root_dir_sha1[:7]))
for p in path_info:
url_args['path'] = p['path']
dir_url = reverse('browse-origin-directory',
kwargs=url_args,
query_params=query_params)
self.assertContains(resp, '<a href="%s">%s</a>' %
(dir_url, p['name']))
self.assertContains(resp, '<li>%s</li>' % filename)
query_string = 'sha1_git:' + content_sha1
url_raw = reverse('browse-content-raw',
kwargs={'query_string': query_string},
query_params={'filename': filename})
self.assertContains(resp, url_raw)
self.assertContains(resp, '<li class="swh-branch">',
count=len(origin_branches))
url_args['path'] = content_path
for branch in origin_branches:
query_params['branch'] = branch['name']
root_dir_branch_url = \
reverse('browse-origin-content',
kwargs=url_args,
query_params=query_params)
self.assertContains(resp, '<a href="%s">' % root_dir_branch_url)
@patch('swh.web.browse.utils.get_origin_visits')
@patch('swh.web.browse.views.origin.get_origin_visit_branches')
@patch('swh.web.browse.views.origin.service')
@patch('swh.web.browse.views.origin.request_content')
@istest
def origin_content_view(self, mock_request_content, mock_service,
mock_get_origin_visit_branches,
mock_get_origin_visits):
stub_content_text_sha1 = stub_content_text_data['checksums']['sha1']
mock_get_origin_visits.return_value = stub_content_origin_visits
mock_get_origin_visit_branches.return_value = stub_content_origin_branches
mock_service.lookup_directory_with_path.return_value = \
{'target': stub_content_text_sha1}
mock_request_content.return_value = stub_content_text_data
mock_service.lookup_origin.return_value = stub_content_origin_info
self.origin_content_view_test(stub_content_origin_info,
stub_content_origin_visits,
stub_content_origin_branches,
stub_content_origin_branch,
stub_content_root_dir,
stub_content_text_sha1,
stub_content_text_path,
stub_content_text_data['raw_data'],
'cpp')
self.origin_content_view_test(stub_content_origin_info,
stub_content_origin_visits,
stub_content_origin_branches,
stub_content_origin_branch,
stub_content_root_dir,
stub_content_text_sha1,
stub_content_text_path,
stub_content_text_data['raw_data'],
'cpp',
visit_id=stub_content_origin_visit_id)
self.origin_content_view_test(stub_content_origin_info,
stub_content_origin_visits,
stub_content_origin_branches,
stub_content_origin_branch,
stub_content_root_dir,
stub_content_text_sha1,
stub_content_text_path,
stub_content_text_data['raw_data'],
'cpp',
timestamp=stub_content_origin_visit_unix_ts)
self.origin_content_view_test(stub_content_origin_info,
stub_content_origin_visits,
stub_content_origin_branches,
stub_content_origin_branch,
stub_content_root_dir,
stub_content_text_sha1,
stub_content_text_path,
stub_content_text_data['raw_data'],
'cpp',
timestamp=stub_content_origin_visit_iso_date)
@nottest
def origin_directory_view(self, origin_info, origin_visits,
origin_branches, origin_branch,
root_directory_sha1, directory_entries,
visit_id=None, timestamp=None, path=None):
dirs = [e for e in directory_entries
if e['type'] == 'dir']
files = [e for e in directory_entries
if e['type'] == 'file']
if not visit_id:
visit_id = origin_visits[-1]['visit']
url_args = {'origin_type': origin_info['type'],
'origin_url': origin_info['url']}
query_params = {}
if timestamp:
url_args['timestamp'] = timestamp
else:
query_params['visit_id'] = visit_id
if path:
url_args['path'] = path
url = reverse('browse-origin-directory',
kwargs=url_args,
query_params=query_params)
resp = self.client.get(url)
self.assertEquals(resp.status_code, 200)
self.assertTemplateUsed('directory.html')
self.assertContains(resp, '<td class="swh-directory">',
count=len(dirs))
self.assertContains(resp, '<td class="swh-content">',
count=len(files))
query_params['branch'] = origin_branch
if timestamp:
url_args['timestamp'] = \
format_utc_iso_date(parse_timestamp(timestamp).isoformat(),
'%Y-%m-%dT%H:%M:%S')
for d in dirs:
dir_path = d['name']
if path:
dir_path = "%s/%s" % (path, d['name'])
dir_url_args = dict(url_args)
dir_url_args['path'] = dir_path
dir_url = reverse('browse-origin-directory',
kwargs=dir_url_args,
query_params=query_params)
self.assertContains(resp, dir_url)
for f in files:
file_path = f['name']
if path:
file_path = "%s/%s" % (path, f['name'])
file_url_args = dict(url_args)
file_url_args['path'] = file_path
file_url = reverse('browse-origin-content',
kwargs=file_url_args,
query_params=query_params)
self.assertContains(resp, file_url)
if 'path' in url_args:
del url_args['path']
root_dir_branch_url = \
reverse('browse-origin-directory',
kwargs=url_args,
query_params=query_params)
nb_bc_paths = 1
if path:
nb_bc_paths = len(path.split('/')) + 1
self.assertContains(resp, '<li class="swh-path">', count=nb_bc_paths)
self.assertContains(resp, '<a href="%s">%s</a>' %
(root_dir_branch_url,
root_directory_sha1[:7]))
self.assertContains(resp, '<li class="swh-branch">',
count=len(origin_branches))
if path:
url_args['path'] = path
for branch in origin_branches:
query_params['branch'] = branch['name']
root_dir_branch_url = \
reverse('browse-origin-directory',
kwargs=url_args,
query_params=query_params)
self.assertContains(resp, '<a href="%s">' % root_dir_branch_url)
@patch('swh.web.browse.utils.get_origin_visits')
@patch('swh.web.browse.views.origin.get_origin_visit_branches')
@patch('swh.web.browse.utils.service')
@patch('swh.web.browse.views.origin.service')
@istest
def origin_root_directory_view(self, mock_origin_service,
mock_utils_service,
mock_get_origin_visit_branches,
mock_get_origin_visits):
mock_get_origin_visits.return_value = stub_origin_visits
mock_get_origin_visit_branches.return_value = stub_origin_branches
mock_utils_service.lookup_directory.return_value = \
stub_origin_root_directory_entries
mock_origin_service.lookup_origin.return_value = stub_origin_info
self.origin_directory_view(stub_origin_info, stub_origin_visits,
stub_origin_branches,
stub_origin_master_branch,
stub_origin_root_directory_sha1,
stub_origin_root_directory_entries)
self.origin_directory_view(stub_origin_info, stub_origin_visits,
stub_origin_branches,
stub_origin_master_branch,
stub_origin_root_directory_sha1,
stub_origin_root_directory_entries,
visit_id=stub_visit_id)
self.origin_directory_view(stub_origin_info, stub_origin_visits,
stub_origin_branches,
stub_origin_master_branch,
stub_origin_root_directory_sha1,
stub_origin_root_directory_entries,
timestamp=stub_visit_unix_ts)
self.origin_directory_view(stub_origin_info, stub_origin_visits,
stub_origin_branches,
stub_origin_master_branch,
stub_origin_root_directory_sha1,
stub_origin_root_directory_entries,
timestamp=stub_visit_iso_date)
@patch('swh.web.browse.utils.get_origin_visits')
@patch('swh.web.browse.views.origin.get_origin_visit_branches')
@patch('swh.web.browse.utils.service')
@patch('swh.web.browse.views.origin.service')
@istest
def origin_sub_directory_view(self, mock_origin_service,
mock_utils_service,
mock_get_origin_visit_branches,
mock_get_origin_visits):
mock_get_origin_visits.return_value = stub_origin_visits
mock_get_origin_visit_branches.return_value = stub_origin_branches
mock_utils_service.lookup_directory.return_value = \
stub_origin_sub_directory_entries
mock_origin_service.lookup_directory_with_path.return_value = \
{'target': '120c39eeb566c66a77ce0e904d29dfde42228adb'}
mock_origin_service.lookup_origin.return_value = stub_origin_info
self.origin_directory_view(stub_origin_info, stub_origin_visits,
stub_origin_branches,
stub_origin_master_branch,
stub_origin_root_directory_sha1,
stub_origin_sub_directory_entries,
path=stub_origin_sub_directory_path)
self.origin_directory_view(stub_origin_info, stub_origin_visits,
stub_origin_branches,
stub_origin_master_branch,
stub_origin_root_directory_sha1,
stub_origin_sub_directory_entries,
visit_id=stub_visit_id,
path=stub_origin_sub_directory_path)
self.origin_directory_view(stub_origin_info, stub_origin_visits,
stub_origin_branches,
stub_origin_master_branch,
stub_origin_root_directory_sha1,
stub_origin_sub_directory_entries,
timestamp=stub_visit_unix_ts,
path=stub_origin_sub_directory_path)
self.origin_directory_view(stub_origin_info, stub_origin_visits,
stub_origin_branches,
stub_origin_master_branch,
stub_origin_root_directory_sha1,
stub_origin_sub_directory_entries,
timestamp=stub_visit_iso_date,
path=stub_origin_sub_directory_path)
@patch('swh.web.browse.views.origin.request_content')
@patch('swh.web.browse.utils.get_origin_visits')
@patch('swh.web.browse.views.origin.get_origin_visit_branches')
@patch('swh.web.browse.utils.service')
@patch('swh.web.browse.views.origin.service')
@istest
def test_origin_request_errors(self, mock_origin_service,
mock_utils_service,
mock_get_origin_visit_branches,
mock_get_origin_visits,
mock_request_content):
mock_origin_service.lookup_origin.side_effect = \
NotFoundExc('origin not found')
url = reverse('browse-origin',
kwargs={'origin_type': 'foo',
'origin_url': 'bar'})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertContains(resp, "origin not found", status_code=404)
mock_origin_service.lookup_origin.side_effect = None
mock_origin_service.lookup_origin.return_value = origin_info_test_data
mock_get_origin_visits.return_value = []
url = reverse('browse-origin-directory',
kwargs={'origin_type': 'foo',
'origin_url': 'bar'})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertContains(resp, "No SWH visit", status_code=404)
mock_get_origin_visits.return_value = stub_origin_visits
mock_get_origin_visit_branches.side_effect = \
NotFoundExc('visit not found')
url = reverse('browse-origin-directory',
kwargs={'origin_type': 'foo',
'origin_url': 'bar'},
query_params={'visit_id': len(stub_origin_visits)+1})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertRegex(resp.content.decode('utf-8'), 'Visit.*not found')
mock_get_origin_visits.return_value = stub_origin_visits
mock_get_origin_visit_branches.side_effect = None
mock_get_origin_visit_branches.return_value = []
url = reverse('browse-origin-directory',
kwargs={'origin_type': 'foo',
'origin_url': 'bar'})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertRegex(resp.content.decode('utf-8'),
'Origin.*is empty')
mock_get_origin_visit_branches.return_value = stub_origin_branches
mock_utils_service.lookup_directory.side_effect = \
NotFoundExc('Directory not found')
url = reverse('browse-origin-directory',
kwargs={'origin_type': 'foo',
'origin_url': 'bar'})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertContains(resp, 'Directory not found', status_code=404)
mock_origin_service.lookup_origin.side_effect = None
mock_origin_service.lookup_origin.return_value = origin_info_test_data
mock_get_origin_visits.return_value = []
url = reverse('browse-origin-content',
kwargs={'origin_type': 'foo',
'origin_url': 'bar',
'path': 'foo'})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertContains(resp, "No SWH visit", status_code=404)
mock_get_origin_visits.return_value = stub_origin_visits
mock_get_origin_visit_branches.side_effect = \
NotFoundExc('visit not found')
url = reverse('browse-origin-content',
kwargs={'origin_type': 'foo',
'origin_url': 'bar',
'path': 'foo'},
query_params={'visit_id': len(stub_origin_visits)+1})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertRegex(resp.content.decode('utf-8'), 'Visit.*not found')
mock_get_origin_visits.return_value = stub_origin_visits
mock_get_origin_visit_branches.side_effect = None
mock_get_origin_visit_branches.return_value = []
url = reverse('browse-origin-content',
kwargs={'origin_type': 'foo',
'origin_url': 'bar',
'path': 'baz'})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertRegex(resp.content.decode('utf-8'),
'Origin.*is empty')
mock_get_origin_visit_branches.return_value = stub_origin_branches
mock_origin_service.lookup_directory_with_path.return_value = \
{'target': stub_content_text_data['checksums']['sha1']}
mock_request_content.side_effect = \
NotFoundExc('Content not found')
url = reverse('browse-origin-content',
kwargs={'origin_type': 'foo',
'origin_url': 'bar',
'path': 'baz'})
resp = self.client.get(url)
self.assertEquals(resp.status_code, 404)
self.assertTemplateUsed('error.html')
self.assertContains(resp, 'Content not found', status_code=404)
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Jul 4, 3:46 PM (1 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3327028
Attached To
R65 Staging repository
Event Timeline
Log In to Comment