Page MenuHomeSoftware Heritage

No OneTemporary

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&regexp=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> &mdash;
Copyright (C) 2015&ndash;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

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

Event Timeline