Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F9338907
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Subscribers
None
View Options
diff --git a/assets/src/bundles/add_forge/create-request.js b/assets/src/bundles/add_forge/create-request.js
index 75ff23c6..31b64f47 100644
--- a/assets/src/bundles/add_forge/create-request.js
+++ b/assets/src/bundles/add_forge/create-request.js
@@ -1,131 +1,124 @@
/**
* Copyright (C) 2022 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
*/
import {handleFetchError, errorMessageFromResponse, csrfPost,
- getHumanReadableDate} from 'utils/functions';
+ getHumanReadableDate, genLink} from 'utils/functions';
import userRequestsFilterCheckboxFn from 'utils/requests-filter-checkbox.ejs';
import {swhSpinnerSrc} from 'utils/constants';
let requestBrowseTable;
const addForgeCheckboxId = 'swh-add-forge-user-filter';
const userRequestsFilterCheckbox = userRequestsFilterCheckboxFn({
'inputId': addForgeCheckboxId,
'checked': true // by default, display only user requests
});
export function onCreateRequestPageLoad() {
$(document).ready(() => {
$('#requestCreateForm').submit(async function(event) {
event.preventDefault();
try {
const response = await csrfPost($(this).attr('action'),
{'Content-Type': 'application/x-www-form-urlencoded'},
$(this).serialize());
handleFetchError(response);
$('#userMessageDetail').empty();
$('#userMessage').text('Your request has been submitted');
$('#userMessage').removeClass('badge-danger');
$('#userMessage').addClass('badge-success');
requestBrowseTable.draw(); // redraw the table to update the list
} catch (errorResponse) {
$('#userMessageDetail').empty();
let errorMessage;
const errorData = await errorResponse.json();
// if (errorResponse.content_type === 'text/plain') { // does not work?
if (errorResponse.status === 409) {
errorMessage = errorData;
} else { // assuming json response
// const exception = errorData['exception'];
errorMessage = errorMessageFromResponse(
errorData, 'An unknown error occurred during the request creation');
}
$('#userMessage').text(errorMessage);
$('#userMessage').removeClass('badge-success');
$('#userMessage').addClass('badge-danger');
}
});
populateRequestBrowseList(); // Load existing requests
});
}
export function populateRequestBrowseList() {
requestBrowseTable = $('#add-forge-request-browse')
.on('error.dt', (e, settings, techNote, message) => {
$('#add-forge-browse-request-error').text(message);
})
.DataTable({
serverSide: true,
processing: true,
language: {
processing: `<img src="${swhSpinnerSrc}"></img>`
},
retrieve: true,
searching: true,
info: false,
// Layout configuration, see [1] for more details
// [1] https://datatables.net/reference/option/dom
dom: '<"row"<"col-sm-3"l><"col-sm-6 text-left user-requests-filter"><"col-sm-3"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-5"i><"col-sm-7"p>>',
ajax: {
'url': Urls.add_forge_request_list_datatables(),
data: (d) => {
const checked = $(`#${addForgeCheckboxId}`).prop('checked');
// If this function is called while the page is loading, 'checked' is
// undefined. As the checkbox defaults to being checked, coerce this to true.
if (swh.webapp.isUserLoggedIn() && (checked === undefined || checked)) {
d.user_requests_only = '1';
}
}
},
fnInitComplete: function() {
if (swh.webapp.isUserLoggedIn()) {
$('div.user-requests-filter').html(userRequestsFilterCheckbox);
$(`#${addForgeCheckboxId}`).on('change', () => {
requestBrowseTable.draw();
});
}
},
columns: [
{
data: 'submission_date',
name: 'submission_date',
render: getHumanReadableDate
},
{
data: 'forge_type',
name: 'forge_type',
render: $.fn.dataTable.render.text()
},
{
data: 'forge_url',
name: 'forge_url',
- render: function(data, type, row) {
- if (type === 'display') {
- let html = '';
- const sanitizedURL = $.fn.dataTable.render.text().display(data);
- html += sanitizedURL;
- html += ` <a href="${sanitizedURL}" target="_blank" rel="noopener noreferrer">` +
- '<i class="mdi mdi-open-in-new" aria-hidden="true"></i></a>';
- return html;
- }
- return data;
+ render: (data, type, row) => {
+ const sanitizedURL = $.fn.dataTable.render.text().display(data);
+ return genLink(sanitizedURL, type, true);
}
},
{
data: 'status',
name: 'status',
render: function(data, type, row, meta) {
return swh.add_forge.formatRequestStatusName(data);
}
}
]
});
}
diff --git a/assets/src/bundles/add_forge/moderation-dashboard.js b/assets/src/bundles/add_forge/moderation-dashboard.js
index 2035ebbe..fd062281 100644
--- a/assets/src/bundles/add_forge/moderation-dashboard.js
+++ b/assets/src/bundles/add_forge/moderation-dashboard.js
@@ -1,72 +1,75 @@
/**
* Copyright (C) 2022 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
*/
-import {getHumanReadableDate} from 'utils/functions';
+import {getHumanReadableDate, genLink} from 'utils/functions';
export function onModerationPageLoad() {
populateModerationList();
}
export async function populateModerationList() {
$('#swh-add-forge-now-moderation-list')
.on('error.dt', (e, settings, techNote, message) => {
$('#swh-add-forge-now-moderation-list-error').text(message);
})
.DataTable({
serverSide: true,
processing: true,
searching: true,
info: false,
dom: '<<"d-flex justify-content-between align-items-center"f' +
'<"#list-exclude">l>rt<"bottom"ip>>',
ajax: {
'url': Urls.add_forge_request_list_datatables()
},
columns: [
{
data: 'id',
name: 'id',
render: function(data, type, row, meta) {
const dashboardUrl = Urls.add_forge_now_request_dashboard(data);
return `<a href=${dashboardUrl}>${data}</a>`;
}
},
{
data: 'submission_date',
name: 'submission_date',
render: getHumanReadableDate
},
{
data: 'forge_type',
name: 'forge_type',
render: $.fn.dataTable.render.text()
},
{
data: 'forge_url',
name: 'forge_url',
- render: $.fn.dataTable.render.text()
+ render: (data, type, row) => {
+ const sanitizedURL = $.fn.dataTable.render.text().display(data);
+ return genLink(sanitizedURL, type, true);
+ }
},
{
data: 'last_moderator',
name: 'last_moderator',
render: $.fn.dataTable.render.text()
},
{
data: 'last_modified_date',
name: 'last_modified_date',
render: getHumanReadableDate
},
{
data: 'status',
name: 'status',
render: function(data, type, row, meta) {
return swh.add_forge.formatRequestStatusName(data);
}
}
]
});
}
diff --git a/assets/src/bundles/admin/deposit.js b/assets/src/bundles/admin/deposit.js
index efb28896..ef8f7dec 100644
--- a/assets/src/bundles/admin/deposit.js
+++ b/assets/src/bundles/admin/deposit.js
@@ -1,203 +1,188 @@
/**
* Copyright (C) 2018-2022 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
*/
-import {getHumanReadableDate} from 'utils/functions';
+import {getHumanReadableDate, genLink} from 'utils/functions';
function genSwhLink(data, type, linkText = '') {
if (type === 'display' && data && data.startsWith('swh')) {
const browseUrl = Urls.browse_swhid(data);
const formattedSWHID = data.replace(/;/g, ';<br/>');
if (!linkText) {
linkText = formattedSWHID;
}
return `<a href="${browseUrl}">${linkText}</a>`;
}
return data;
}
-function genLink(data, type, openInNewTab = false, linkText = '') {
- if (type === 'display' && data) {
- const sData = encodeURI(data);
- if (!linkText) {
- linkText = sData;
- }
- let attrs = '';
- if (openInNewTab) {
- attrs = 'target="_blank" rel="noopener noreferrer"';
- }
- return `<a href="${sData}" ${attrs}>${linkText}</a>`;
- }
- return data;
-}
-
export function initDepositAdmin(username, isStaff) {
let depositsTable;
$(document).ready(() => {
$.fn.dataTable.ext.errMode = 'none';
depositsTable = $('#swh-admin-deposit-list')
.on('error.dt', (e, settings, techNote, message) => {
$('#swh-admin-deposit-list-error').text(message);
})
.DataTable({
serverSide: true,
processing: true,
// let's define the order of table options display
// f: (f)ilter
// l: (l)ength changing
// r: p(r)ocessing
// t: (t)able
// i: (i)nfo
// p: (p)agination
// see https://datatables.net/examples/basic_init/dom.html
dom: '<<"d-flex justify-content-between align-items-center"f' +
'<"#list-exclude">l>rt<"bottom"ip>>',
// div#list-exclude is a custom filter added next to dataTable
// initialization below through js dom manipulation, see
// https://datatables.net/examples/advanced_init/dom_toolbar.html
ajax: {
url: Urls.admin_deposit_list(),
data: d => {
d.excludePattern = $('#swh-admin-deposit-list-exclude-filter').val();
}
},
columns: [
{
data: 'id',
name: 'id'
},
{
data: 'type',
name: 'type'
},
{
data: 'uri',
name: 'uri',
render: (data, type, row) => {
const sanitizedURL = $.fn.dataTable.render.text().display(data);
let swhLink = '';
let originLink = '';
if (row.swhid_context && data) {
swhLink = genSwhLink(row.swhid_context, type, sanitizedURL);
} else if (data) {
swhLink = sanitizedURL;
}
if (data) {
originLink = genLink(sanitizedURL, type, true,
'<i class="mdi mdi-open-in-new" aria-hidden="true"></i>');
}
return swhLink + ' ' + originLink;
}
},
{
data: 'reception_date',
name: 'reception_date',
render: getHumanReadableDate
},
{
data: 'status',
name: 'status'
},
{
data: 'raw_metadata',
name: 'raw_metadata',
render: (data, type, row) => {
if (type === 'display') {
if (row.raw_metadata) {
return `<button class="btn btn-default metadata">display</button>`;
}
}
return data;
}
},
{
data: 'status_detail',
name: 'status_detail',
render: (data, type, row) => {
if (type === 'display' && data) {
let text = data;
if (typeof data === 'object') {
text = JSON.stringify(data, null, 4);
}
return `<div style="width: 200px; white-space: pre; overflow-x: auto;">${text}</div>`;
}
return data;
},
orderable: false,
visible: false
},
{
data: 'swhid',
name: 'swhid',
render: (data, type, row) => {
return genSwhLink(data, type);
},
orderable: false,
visible: false
},
{
data: 'swhid_context',
name: 'swhid_context',
render: (data, type, row) => {
return genSwhLink(data, type);
},
orderable: false,
visible: false
}
],
scrollX: true,
scrollY: '50vh',
scrollCollapse: true,
order: [[0, 'desc']]
});
// Some more customization is needed on the table
$('div#list-exclude').html(`<div id="swh-admin-deposit-list-exclude-wrapper">
<div id="swh-admin-deposit-list-exclude-div-wrapper" class="dataTables_filter">
<label>
Exclude:<input id="swh-admin-deposit-list-exclude-filter"
type="search"
value="check-deposit"
class="form-control form-control-sm"
placeholder="exclude-pattern" aria-controls="swh-admin-deposit-list">
</input>
</label>
</div>
</div>
`);
// Show a modal when the "metadata" button is clicked
$('#swh-admin-deposit-list tbody').on('click', 'tr button.metadata', function() {
var row = depositsTable.row(this.parentNode.parentNode).data();
var metadata = row.raw_metadata;
var escapedMetadata = $('<div/>').text(metadata).html();
swh.webapp.showModalHtml(`Metadata of deposit ${row.id}`,
`<pre style="max-height: 75vh;"><code class="xml">${escapedMetadata}</code></pre>`,
'90%');
swh.webapp.highlightCode();
});
// Adding exclusion pattern update behavior, when typing, update search
$('#swh-admin-deposit-list-exclude-filter').keyup(function() {
depositsTable.draw();
});
// at last draw the table
depositsTable.draw();
});
$('a.toggle-col').on('click', function(e) {
e.preventDefault();
var column = depositsTable.column($(this).attr('data-column'));
column.visible(!column.visible());
if (column.visible()) {
$(this).removeClass('col-hidden');
} else {
$(this).addClass('col-hidden');
}
});
}
diff --git a/assets/src/utils/functions.js b/assets/src/utils/functions.js
index 2c92a5f6..90960f1d 100644
--- a/assets/src/utils/functions.js
+++ b/assets/src/utils/functions.js
@@ -1,168 +1,184 @@
/**
- * Copyright (C) 2018-2020 The Software Heritage developers
+ * Copyright (C) 2018-2022 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
*/
// utility functions
import Cookies from 'js-cookie';
export function handleFetchError(response) {
if (!response.ok) {
throw response;
}
return response;
}
export function handleFetchErrors(responses) {
for (let i = 0; i < responses.length; ++i) {
if (!responses[i].ok) {
throw responses[i];
}
}
return responses;
}
export function errorMessageFromResponse(errorData, defaultMessage) {
let errorMessage = '';
try {
const reason = JSON.parse(errorData['reason']);
Object.entries(reason).forEach((keys, _) => {
const key = keys[0];
const message = keys[1][0]; // take only the first issue
errorMessage += `\n${key}: ${message}`;
});
} catch (_) {
errorMessage = errorData['reason']; // can't parse it, leave it raw
}
return errorMessage ? `Error: ${errorMessage}` : defaultMessage;
}
export function staticAsset(asset) {
return `${__STATIC__}${asset}`;
}
export function csrfPost(url, headers = {}, body = null) {
headers['X-CSRFToken'] = Cookies.get('csrftoken');
return fetch(url, {
credentials: 'include',
headers: headers,
method: 'POST',
body: body
});
}
export function isGitRepoUrl(url, pathPrefix = '/') {
const allowedProtocols = ['http:', 'https:', 'git:'];
if (allowedProtocols.find(protocol => protocol === url.protocol) === undefined) {
return false;
}
if (!url.pathname.startsWith(pathPrefix)) {
return false;
}
const re = new RegExp('[\\w\\.-]+\\/?(?!=.git)(?:\\.git\\/?)?$');
return re.test(url.pathname.slice(pathPrefix.length));
};
export function removeUrlFragment() {
history.replaceState('', document.title, window.location.pathname + window.location.search);
}
export function selectText(startNode, endNode) {
const selection = window.getSelection();
selection.removeAllRanges();
const range = document.createRange();
range.setStart(startNode, 0);
if (endNode.nodeName !== '#text') {
range.setEnd(endNode, endNode.childNodes.length);
} else {
range.setEnd(endNode, endNode.textContent.length);
}
selection.addRange(range);
}
export function htmlAlert(type, message, closable = false) {
let closeButton = '';
let extraClasses = '';
if (closable) {
closeButton =
`<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>`;
extraClasses = 'alert-dismissible';
}
return `<div class="alert alert-${type} ${extraClasses}" role="alert">${message}${closeButton}</div>`;
}
export function isValidURL(string) {
try {
new URL(string);
} catch (_) {
return false;
}
return true;
}
export async function isArchivedOrigin(originPath) {
if (!isValidURL(originPath)) {
// Not a valid URL, return immediately
return false;
} else {
const response = await fetch(Urls.api_1_origin(originPath));
return response.ok && response.status === 200; // Success response represents an archived origin
}
}
async function getCanonicalGithubOriginURL(ownerRepo) {
const ghApiResponse = await fetch(`https://api.github.com/repos/${ownerRepo}`);
if (ghApiResponse.ok && ghApiResponse.status === 200) {
const ghApiResponseData = await ghApiResponse.json();
return ghApiResponseData.html_url;
}
}
export async function getCanonicalOriginURL(originUrl) {
let originUrlLower = originUrl.toLowerCase();
// github.com URL processing
const ghUrlRegex = /^http[s]*:\/\/github.com\//;
if (originUrlLower.match(ghUrlRegex)) {
// remove trailing .git
if (originUrlLower.endsWith('.git')) {
originUrlLower = originUrlLower.slice(0, -4);
}
// remove trailing slash
if (originUrlLower.endsWith('/')) {
originUrlLower = originUrlLower.slice(0, -1);
}
// extract {owner}/{repo}
const ownerRepo = originUrlLower.replace(ghUrlRegex, '');
// fetch canonical URL from github Web API
const url = await getCanonicalGithubOriginURL(ownerRepo);
if (url) {
return url;
}
}
const ghpagesUrlRegex = /^http[s]*:\/\/(?<owner>[^/]+).github.io\/(?<repo>[^/]+)\/?.*/;
const parsedUrl = originUrlLower.match(ghpagesUrlRegex);
if (parsedUrl) {
const ownerRepo = `${parsedUrl.groups.owner}/${parsedUrl.groups.repo}`;
// fetch canonical URL from github Web API
const url = await getCanonicalGithubOriginURL(ownerRepo);
if (url) {
return url;
}
}
return originUrl;
}
export function getHumanReadableDate(data) {
// Display iso format date string into a human readable date
// This is expected to be used by date field in datatable listing views
// Example: 3/24/2022, 10:31:08 AM
const date = new Date(data);
return date.toLocaleString();
}
+
+export function genLink(sanitizedUrl, type, openInNewTab = false, linkText = '') {
+ // Display link. It's up to the caller to sanitize sanitizedUrl first.
+ if (type === 'display' && sanitizedUrl) {
+ const encodedSanitizedUrl = encodeURI(sanitizedUrl);
+ if (!linkText) {
+ linkText = encodedSanitizedUrl;
+ }
+ let attrs = '';
+ if (openInNewTab) {
+ attrs = 'target="_blank" rel="noopener noreferrer"';
+ }
+ return `<a href="${encodedSanitizedUrl}" ${attrs}>${linkText}</a>`;
+ }
+ return sanitizedUrl;
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Jul 4 2025, 9:14 AM (6 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3246040
Attached To
rDWAPPS Web applications
Event Timeline
Log In to Comment