diff --git a/swh/web/assets/src/bundles/admin/origin-save.js b/swh/web/assets/src/bundles/admin/origin-save.js index 6c5d6b14..41d56cb2 100644 --- a/swh/web/assets/src/bundles/admin/origin-save.js +++ b/swh/web/assets/src/bundles/admin/origin-save.js @@ -1,259 +1,249 @@ /** * Copyright (C) 2018 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} from 'utils/functions'; +import {handleFetchError, csrfPost} from 'utils/functions'; let authorizedOriginTable; let unauthorizedOriginTable; let pendingSaveRequestsTable; let acceptedSaveRequestsTable; let rejectedSaveRequestsTable; function enableRowSelection(tableSel) { $(`${tableSel} tbody`).on('click', 'tr', function() { if ($(this).hasClass('selected')) { $(this).removeClass('selected'); } else { $(`${tableSel} tr.selected`).removeClass('selected'); $(this).addClass('selected'); } }); } export function initOriginSaveAdmin() { $(document).ready(() => { authorizedOriginTable = $('#swh-authorized-origin-urls').DataTable({ serverSide: true, ajax: Urls.admin_origin_save_authorized_urls_list(), columns: [{data: 'url', name: 'url'}], scrollY: '50vh', scrollCollapse: true, info: false }); enableRowSelection('#swh-authorized-origin-urls'); unauthorizedOriginTable = $('#swh-unauthorized-origin-urls').DataTable({ serverSide: true, ajax: Urls.admin_origin_save_unauthorized_urls_list(), columns: [{data: 'url', name: 'url'}], scrollY: '50vh', scrollCollapse: true, info: false }); enableRowSelection('#swh-unauthorized-origin-urls'); let columnsData = [ { data: 'save_request_date', name: 'request_date', render: (data, type, row) => { if (type === 'display') { let date = new Date(data); return date.toLocaleString(); } return data; } }, { data: 'origin_type', name: 'origin_type' }, { data: 'origin_url', name: 'origin_url', render: (data, type, row) => { if (type === 'display') { return `${data}`; } return data; } } ]; pendingSaveRequestsTable = $('#swh-origin-save-pending-requests').DataTable({ serverSide: true, ajax: Urls.browse_origin_save_requests_list('pending'), columns: columnsData, scrollY: '50vh', scrollCollapse: true, order: [[0, 'desc']] }); enableRowSelection('#swh-origin-save-pending-requests'); rejectedSaveRequestsTable = $('#swh-origin-save-rejected-requests').DataTable({ serverSide: true, ajax: Urls.browse_origin_save_requests_list('rejected'), columns: columnsData, scrollY: '50vh', scrollCollapse: true, order: [[0, 'desc']] }); columnsData.push({ data: 'save_task_status', name: 'save_task_status', render: (data, type, row) => { if (data === 'succeed') { let browseOriginUrl = Urls.browse_origin(row.origin_url); return `${data}`; } return data; } }); acceptedSaveRequestsTable = $('#swh-origin-save-accepted-requests').DataTable({ serverSide: true, ajax: Urls.browse_origin_save_requests_list('accepted'), columns: columnsData, scrollY: '50vh', scrollCollapse: true, order: [[0, 'desc']] }); $('#swh-origin-save-requests-nav-item').on('shown.bs.tab', () => { pendingSaveRequestsTable.draw(); }); $('#swh-origin-save-url-filters-nav-item').on('shown.bs.tab', () => { authorizedOriginTable.draw(); }); $('#swh-authorized-origins-tab').on('shown.bs.tab', () => { authorizedOriginTable.draw(); }); $('#swh-unauthorized-origins-tab').on('shown.bs.tab', () => { unauthorizedOriginTable.draw(); }); $('#swh-save-requests-pending-tab').on('shown.bs.tab', () => { pendingSaveRequestsTable.draw(); }); $('#swh-save-requests-accepted-tab').on('shown.bs.tab', () => { acceptedSaveRequestsTable.draw(); }); $('#swh-save-requests-rejected-tab').on('shown.bs.tab', () => { rejectedSaveRequestsTable.draw(); }); $('#swh-save-requests-pending-tab').click(() => { pendingSaveRequestsTable.ajax.reload(null, false); }); $('#swh-save-requests-accepted-tab').click(() => { acceptedSaveRequestsTable.ajax.reload(null, false); }); $('#swh-save-requests-rejected-tab').click(() => { rejectedSaveRequestsTable.ajax.reload(null, false); }); }); } -function post(url) { - return fetch(url, { - credentials: 'include', - headers: { - 'X-CSRFToken': Cookies.get('csrftoken') - }, - method: 'POST' - }); -} - export function addAuthorizedOriginUrl() { let originUrl = $('#swh-authorized-url-prefix').val(); let addOriginUrl = Urls.admin_origin_save_add_authorized_url(originUrl); - post(addOriginUrl) + csrfPost(addOriginUrl) .then(handleFetchError) .then(() => { authorizedOriginTable.row.add({'url': originUrl}).draw(); }) .catch(response => { swh.webapp.showModalMessage('Duplicated origin url prefix', 'The provided origin url prefix is already registered in the authorized list.'); }); } export function removeAuthorizedOriginUrl() { let originUrl = $('#swh-authorized-origin-urls tr.selected').text(); if (originUrl) { let removeOriginUrl = Urls.admin_origin_save_remove_authorized_url(originUrl); - post(removeOriginUrl) + csrfPost(removeOriginUrl) .then(handleFetchError) .then(() => { authorizedOriginTable.row('.selected').remove().draw(); }) .catch(() => {}); } } export function addUnauthorizedOriginUrl() { let originUrl = $('#swh-unauthorized-url-prefix').val(); let addOriginUrl = Urls.admin_origin_save_add_unauthorized_url(originUrl); - post(addOriginUrl) + csrfPost(addOriginUrl) .then(handleFetchError) .then(() => { unauthorizedOriginTable.row.add({'url': originUrl}).draw(); }) .catch(() => { swh.webapp.showModalMessage('Duplicated origin url prefix', 'The provided origin url prefix is already registered in the unauthorized list.'); }); } export function removeUnauthorizedOriginUrl() { let originUrl = $('#swh-unauthorized-origin-urls tr.selected').text(); if (originUrl) { let removeOriginUrl = Urls.admin_origin_save_remove_unauthorized_url(originUrl); - post(removeOriginUrl) + csrfPost(removeOriginUrl) .then(handleFetchError) .then(() => { unauthorizedOriginTable.row('.selected').remove().draw(); }) .catch(() => {}); } } export function acceptOriginSaveRequest() { let selectedRow = pendingSaveRequestsTable.row('.selected'); if (selectedRow.length) { let acceptOriginSaveRequestCallback = () => { let rowData = selectedRow.data(); let acceptSaveRequestUrl = Urls.admin_origin_save_request_accept(rowData['origin_type'], rowData['origin_url']); - post(acceptSaveRequestUrl) + csrfPost(acceptSaveRequestUrl) .then(() => { pendingSaveRequestsTable.ajax.reload(null, false); }); }; swh.webapp.showModalConfirm('Accept origin save request ?', 'Are you sure to accept this origin save request ?', acceptOriginSaveRequestCallback); } } export function rejectOriginSaveRequest() { let selectedRow = pendingSaveRequestsTable.row('.selected'); if (selectedRow.length) { let rejectOriginSaveRequestCallback = () => { let rowData = selectedRow.data(); let rejectSaveRequestUrl = Urls.admin_origin_save_request_reject(rowData['origin_type'], rowData['origin_url']); - post(rejectSaveRequestUrl) + csrfPost(rejectSaveRequestUrl) .then(() => { pendingSaveRequestsTable.ajax.reload(null, false); }); }; swh.webapp.showModalConfirm('Reject origin save request ?', 'Are you sure to reject this origin save request ?', rejectOriginSaveRequestCallback); } } diff --git a/swh/web/assets/src/bundles/browse/origin-save.js b/swh/web/assets/src/bundles/browse/origin-save.js index e9be7290..5c20b665 100644 --- a/swh/web/assets/src/bundles/browse/origin-save.js +++ b/swh/web/assets/src/bundles/browse/origin-save.js @@ -1,161 +1,158 @@ /** * Copyright (C) 2018 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} from 'utils/functions'; +import {handleFetchError, csrfPost} from 'utils/functions'; import {validate} from 'validate.js'; let saveRequestsTable; export function initOriginSave() { $(document).ready(() => { fetch(Urls.browse_origin_save_types_list()) .then(response => response.json()) .then(data => { for (let originType of data) { $('#swh-input-origin-type').append(``); } }); saveRequestsTable = $('#swh-origin-save-requests').DataTable({ serverSide: true, ajax: Urls.browse_origin_save_requests_list('all'), columns: [ { data: 'save_request_date', name: 'request_date', render: (data, type, row) => { if (type === 'display') { let date = new Date(data); return date.toLocaleString(); } return data; } }, { data: 'origin_type', name: 'origin_type' }, { data: 'origin_url', name: 'origin_url', render: (data, type, row) => { if (type === 'display') { return `${data}`; } return data; } }, { data: 'save_request_status', name: 'status' }, { data: 'save_task_status', name: 'save_task_status', render: (data, type, row) => { if (data === 'succeed') { let browseOriginUrl = Urls.browse_origin(row.origin_url); return `${data}`; } return data; } } ], scrollY: '50vh', scrollCollapse: true, order: [[0, 'desc']] }); setInterval(() => { saveRequestsTable.ajax.reload(null, false); }, 5000); $('#swh-origin-save-requests-list-tab').on('shown.bs.tab', () => { saveRequestsTable.draw(); }); $('#swh-save-origin-form').submit(event => { event.preventDefault(); event.stopPropagation(); if (event.target.checkValidity()) { $(event.target).removeClass('was-validated'); let originType = $('#swh-input-origin-type').val(); let originUrl = $('#swh-input-origin-url').val(); let addSaveOriginRequestUrl = Urls.browse_origin_save_request(originType, originUrl); let grecaptchaData = {'g-recaptcha-response': grecaptcha.getResponse()}; - fetch(addSaveOriginRequestUrl, { - credentials: 'include', - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'X-CSRFToken': Cookies.get('csrftoken') - }, - body: JSON.stringify(grecaptchaData)}) + let headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }; + let body = JSON.stringify(grecaptchaData); + csrfPost(addSaveOriginRequestUrl, headers, body) .then(handleFetchError) .then(response => response.json()) .then(data => { if (data.save_request_status === 'accepted') { $('#swh-origin-save-request-status').css('color', 'green'); $('#swh-origin-save-request-status').text( 'The origin save request has been accepted and will be processed as soon as possible.'); } else { $('#swh-origin-save-request-status').css('color', '#fecd1b'); $('#swh-origin-save-request-status').text( 'The origin save request has been put in pending state and may be accepted for processing after manual review.'); } grecaptcha.reset(); }) .catch(response => { if (response.status === 403) { $('#swh-origin-save-request-status').css('color', 'red'); $('#swh-origin-save-request-status').text( 'The origin save request has been rejected because the reCAPTCHA could not be validated or the provided origin url is blacklisted.'); } grecaptcha.reset(); }); } else { $(event.target).addClass('was-validated'); } }); $('#swh-show-origin-save-requests-list').on('click', (event) => { event.preventDefault(); $('.nav-tabs a[href="#swh-origin-save-requests-list"]').tab('show'); }); $('#swh-input-origin-url').on('input', function(event) { let originUrl = $(this).val(); $('#swh-input-origin-type option').each(function() { let val = $(this).val(); if (val && originUrl.includes(val)) { $(this).prop('selected', true); } }); }); }); } export function validateSaveOriginUrl(input) { let validUrl = validate({website: input.value}, { website: { url: { schemes: ['http', 'https', 'svn'] } } }); if (validUrl === undefined) { input.setCustomValidity(''); } else { input.setCustomValidity('The origin url is not valid'); } } diff --git a/swh/web/assets/src/bundles/vault/vault-ui.js b/swh/web/assets/src/bundles/vault/vault-ui.js index b89cf2d2..384dd6ab 100644 --- a/swh/web/assets/src/bundles/vault/vault-ui.js +++ b/swh/web/assets/src/bundles/vault/vault-ui.js @@ -1,256 +1,256 @@ /** * Copyright (C) 2018 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, handleFetchErrors} from 'utils/functions'; +import {handleFetchError, handleFetchErrors, csrfPost} from 'utils/functions'; let progress = `
;`; let pollingInterval = 5000; let checkVaultId; function updateProgressBar(progressBar, cookingTask) { if (cookingTask.status === 'new') { progressBar.css('background-color', 'rgba(128, 128, 128, 0.5)'); } else if (cookingTask.status === 'pending') { progressBar.css('background-color', 'rgba(0, 0, 255, 0.5)'); } else if (cookingTask.status === 'done') { progressBar.css('background-color', '#5cb85c'); } else if (cookingTask.status === 'failed') { progressBar.css('background-color', 'rgba(255, 0, 0, 0.5)'); progressBar.css('background-image', 'none'); } progressBar.text(cookingTask.progress_message || cookingTask.status); if (cookingTask.status === 'new' || cookingTask.status === 'pending') { progressBar.addClass('progress-bar-animated'); } else { progressBar.removeClass('progress-bar-striped'); } } let recookTask; // called when the user wants to download a cooked archive export function fetchCookedObject(fetchUrl) { recookTask = null; // first, check if the link is still available from the vault fetch(fetchUrl) .then(response => { // link is still alive, proceed to download if (response.ok) { $('#vault-fetch-iframe').attr('src', fetchUrl); // link is dead } else { // get the associated cooking task let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); for (let i = 0; i < vaultCookingTasks.length; ++i) { if (vaultCookingTasks[i].fetch_url === fetchUrl) { recookTask = vaultCookingTasks[i]; break; } } // display a modal asking the user if he wants to recook the archive $('#vault-recook-object-modal').modal('show'); } }); } // called when the user wants to recook an archive // for which the download link is not available anymore export function recookObject() { if (recookTask) { // stop cookink tasks status polling clearTimeout(checkVaultId); // build cook request url let cookingUrl; if (recookTask.object_type === 'directory') { cookingUrl = Urls.vault_cook_directory(recookTask.object_id); } else { cookingUrl = Urls.vault_cook_revision_gitfast(recookTask.object_id); } if (recookTask.email) { cookingUrl += '?email=' + recookTask.email; } // request archive cooking - fetch(cookingUrl, {credentials: 'omit', method: 'POST'}) + csrfPost(cookingUrl) .then(handleFetchError) .then(() => { // update task status recookTask.status = 'new'; let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); for (let i = 0; i < vaultCookingTasks.length; ++i) { if (vaultCookingTasks[i].object_id === recookTask.object_id) { vaultCookingTasks[i] = recookTask; break; } } // save updated tasks to local storage localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks)); // restart cooking tasks status polling checkVaultCookingTasks(); // hide recook archive modal $('#vault-recook-object-modal').modal('hide'); }) // something went wrong .catch(() => { checkVaultCookingTasks(); $('#vault-recook-object-modal').modal('hide'); }); } } function checkVaultCookingTasks() { let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); if (!vaultCookingTasks || vaultCookingTasks.length === 0) { $('.swh-vault-table tbody tr').remove(); checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval); return; } let cookingTaskRequests = []; let tasks = {}; let currentObjectIds = []; for (let i = 0; i < vaultCookingTasks.length; ++i) { let cookingTask = vaultCookingTasks[i]; currentObjectIds.push(cookingTask.object_id); tasks[cookingTask.object_id] = cookingTask; let cookingUrl; if (cookingTask.object_type === 'directory') { cookingUrl = Urls.vault_cook_directory(cookingTask.object_id); } else { cookingUrl = Urls.vault_cook_revision_gitfast(cookingTask.object_id); } if (cookingTask.status !== 'done' && cookingTask.status !== 'failed') { cookingTaskRequests.push(fetch(cookingUrl)); } } $('.swh-vault-table tbody tr').each((i, row) => { let objectId = $(row).find('.vault-object-id').data('object-id'); if ($.inArray(objectId, currentObjectIds) === -1) { $(row).remove(); } }); Promise.all(cookingTaskRequests) .then(handleFetchErrors) .then(responses => Promise.all(responses.map(r => r.json()))) .then(cookingTasks => { let table = $('#vault-cooking-tasks tbody'); for (let i = 0; i < cookingTasks.length; ++i) { let cookingTask = tasks[cookingTasks[i].obj_id]; cookingTask.status = cookingTasks[i].status; cookingTask.fetch_url = cookingTasks[i].fetch_url; cookingTask.progress_message = cookingTasks[i].progress_message; } for (let i = 0; i < vaultCookingTasks.length; ++i) { let cookingTask = vaultCookingTasks[i]; let rowTask = $('#vault-task-' + cookingTask.object_id); let downloadLinkWait = 'Waiting for download link to be available'; if (!rowTask.length) { let browseUrl; if (cookingTask.object_type === 'directory') { browseUrl = Urls.browse_directory(cookingTask.object_id); } else { browseUrl = Urls.browse_revision(cookingTask.object_id); } let progressBar = $.parseHTML(progress)[0]; let progressBarContent = $(progressBar).find('.progress-bar'); updateProgressBar(progressBarContent, cookingTask); let tableRow; if (cookingTask.object_type === 'directory') { tableRow = ``; } else { tableRow = ``; } tableRow += ''; if (cookingTask.object_type === 'directory') { tableRow += 'directory'; } else { tableRow += 'revision'; } tableRow += `${cookingTask.object_id}`; tableRow += `${progressBar.outerHTML}`; let downloadLink = downloadLinkWait; if (cookingTask.status === 'done') { downloadLink = `'; } else if (cookingTask.status === 'failed') { downloadLink = ''; } tableRow += `${downloadLink}`; tableRow += ''; table.prepend(tableRow); } else { let progressBar = rowTask.find('.progress-bar'); updateProgressBar(progressBar, cookingTask); let downloadLink = rowTask.find('.vault-dl-link'); if (cookingTask.status === 'done') { downloadLink[0].innerHTML = `'; } else if (cookingTask.status === 'failed') { downloadLink[0].innerHTML = ''; } else if (cookingTask.status === 'new') { downloadLink[0].innerHTML = downloadLinkWait; } } } localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks)); checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval); }) .catch(() => {}); } export function initUi() { $('#vault-tasks-toggle-selection').change(event => { $('.vault-task-toggle-selection').prop('checked', event.currentTarget.checked); }); $('#vault-remove-tasks').click(() => { clearTimeout(checkVaultId); let tasksToRemove = []; $('.swh-vault-table tbody tr').each((i, row) => { let taskSelected = $(row).find('.vault-task-toggle-selection').prop('checked'); if (taskSelected) { let objectId = $(row).find('.vault-object-id').data('object-id'); tasksToRemove.push(objectId); $(row).remove(); } }); let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); vaultCookingTasks = $.grep(vaultCookingTasks, task => { return $.inArray(task.object_id, tasksToRemove) === -1; }); localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks)); $('#vault-tasks-toggle-selection').prop('checked', false); checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval); }); checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval); $(document).on('shown.bs.tab', 'a[data-toggle="tab"]', e => { if (e.currentTarget.text.trim() === 'Vault') { clearTimeout(checkVaultId); checkVaultCookingTasks(); } }); window.onfocus = () => { clearTimeout(checkVaultId); checkVaultCookingTasks(); }; } diff --git a/swh/web/assets/src/utils/functions.js b/swh/web/assets/src/utils/functions.js index a56a91da..c72ecff2 100644 --- a/swh/web/assets/src/utils/functions.js +++ b/swh/web/assets/src/utils/functions.js @@ -1,28 +1,38 @@ /** * Copyright (C) 2018 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 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 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 + }); +}