diff --git a/assets/src/bundles/admin/origin-save.js b/assets/src/bundles/admin/origin-save.js index f711c99a..69d942df 100644 --- a/assets/src/bundles/admin/origin-save.js +++ b/assets/src/bundles/admin/origin-save.js @@ -1,409 +1,401 @@ /** * 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 {handleFetchError, csrfPost, htmlAlert, getHumanReadableDate} from 'utils/functions'; import {swhSpinnerSrc} from 'utils/constants'; 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'); $(tableSel).closest('.tab-pane').find('.swh-action-need-selection').prop('disabled', true); } else { $(`${tableSel} tr.selected`).removeClass('selected'); $(this).addClass('selected'); $(tableSel).closest('.tab-pane').find('.swh-action-need-selection').prop('disabled', false); } }); } export function initOriginSaveAdmin() { $(document).ready(() => { $.fn.dataTable.ext.errMode = 'throw'; 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'); swh.webapp.addJumpToPagePopoverToDataTable(authorizedOriginTable); 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'); swh.webapp.addJumpToPagePopoverToDataTable(unauthorizedOriginTable); const columnsData = [ { data: 'id', name: 'id', visible: false, searchable: false }, { data: 'save_request_date', name: 'request_date', render: getHumanReadableDate }, { data: 'visit_type', name: 'visit_type' }, { data: 'origin_url', name: 'origin_url', render: (data, type, row) => { if (type === 'display') { let html = ''; const sanitizedURL = $.fn.dataTable.render.text().display(data); if (row.save_task_status === 'succeeded') { let browseOriginUrl = `${Urls.browse_origin()}?origin_url=${encodeURIComponent(sanitizedURL)}`; if (row.visit_date) { browseOriginUrl += `&timestamp=${encodeURIComponent(row.visit_date)}`; } html += `${sanitizedURL}`; } else { html += sanitizedURL; } html += ` ` + ''; return html; } return data; } } ]; pendingSaveRequestsTable = $('#swh-origin-save-pending-requests').DataTable({ serverSide: true, processing: true, language: { processing: `` }, ajax: Urls.origin_save_requests_list('pending'), searchDelay: 1000, columns: columnsData, scrollY: '50vh', scrollCollapse: true, order: [[0, 'desc']], responsive: { details: { type: 'none' } } }); enableRowSelection('#swh-origin-save-pending-requests'); swh.webapp.addJumpToPagePopoverToDataTable(pendingSaveRequestsTable); columnsData.push({ name: 'info', render: (data, type, row) => { if (row.save_task_status === 'succeeded' || row.save_task_status === 'failed' || row.note != null) { return ``; } else { return ''; } } }); rejectedSaveRequestsTable = $('#swh-origin-save-rejected-requests').DataTable({ serverSide: true, processing: true, language: { processing: `` }, ajax: Urls.origin_save_requests_list('rejected'), searchDelay: 1000, columns: columnsData, scrollY: '50vh', scrollCollapse: true, order: [[0, 'desc']], responsive: { details: { type: 'none' } } }); enableRowSelection('#swh-origin-save-rejected-requests'); swh.webapp.addJumpToPagePopoverToDataTable(rejectedSaveRequestsTable); columnsData.splice(columnsData.length - 1, 0, { data: 'save_task_status', name: 'save_task_status' }); acceptedSaveRequestsTable = $('#swh-origin-save-accepted-requests').DataTable({ serverSide: true, processing: true, language: { processing: `` }, ajax: Urls.origin_save_requests_list('accepted'), searchDelay: 1000, columns: columnsData, scrollY: '50vh', scrollCollapse: true, order: [[0, 'desc']], responsive: { details: { type: 'none' } } }); enableRowSelection('#swh-origin-save-accepted-requests'); swh.webapp.addJumpToPagePopoverToDataTable(acceptedSaveRequestsTable); - $('#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); }); $('body').on('click', e => { if ($(e.target).parents('.popover').length > 0) { e.stopPropagation(); } else if ($(e.target).parents('.swh-save-request-info').length === 0) { $('.swh-save-request-info').popover('dispose'); } }); }); } export async function addAuthorizedOriginUrl() { const originUrl = $('#swh-authorized-url-prefix').val(); const addOriginUrl = Urls.admin_origin_save_add_authorized_url(originUrl); try { const response = await csrfPost(addOriginUrl); handleFetchError(response); authorizedOriginTable.row.add({'url': originUrl}).draw(); $('.swh-add-authorized-origin-status').html( htmlAlert('success', 'The origin url prefix has been successfully added in the authorized list.', true) ); } catch (_) { $('.swh-add-authorized-origin-status').html( htmlAlert('warning', 'The provided origin url prefix is already registered in the authorized list.', true) ); } } export async function removeAuthorizedOriginUrl() { const originUrl = $('#swh-authorized-origin-urls tr.selected').text(); if (originUrl) { const removeOriginUrl = Urls.admin_origin_save_remove_authorized_url(originUrl); try { const response = await csrfPost(removeOriginUrl); handleFetchError(response); authorizedOriginTable.row('.selected').remove().draw(); } catch (_) {} } } export async function addUnauthorizedOriginUrl() { const originUrl = $('#swh-unauthorized-url-prefix').val(); const addOriginUrl = Urls.admin_origin_save_add_unauthorized_url(originUrl); try { const response = await csrfPost(addOriginUrl); handleFetchError(response); unauthorizedOriginTable.row.add({'url': originUrl}).draw(); $('.swh-add-unauthorized-origin-status').html( htmlAlert('success', 'The origin url prefix has been successfully added in the unauthorized list.', true) ); } catch (_) { $('.swh-add-unauthorized-origin-status').html( htmlAlert('warning', 'The provided origin url prefix is already registered in the unauthorized list.', true) ); } } export async function removeUnauthorizedOriginUrl() { const originUrl = $('#swh-unauthorized-origin-urls tr.selected').text(); if (originUrl) { const removeOriginUrl = Urls.admin_origin_save_remove_unauthorized_url(originUrl); try { const response = await csrfPost(removeOriginUrl); handleFetchError(response); unauthorizedOriginTable.row('.selected').remove().draw(); } catch (_) {}; } } export function acceptOriginSaveRequest() { const selectedRow = pendingSaveRequestsTable.row('.selected'); if (selectedRow.length) { const acceptOriginSaveRequestCallback = async() => { const rowData = selectedRow.data(); const acceptSaveRequestUrl = Urls.admin_origin_save_request_accept(rowData['visit_type'], rowData['origin_url']); await csrfPost(acceptSaveRequestUrl); pendingSaveRequestsTable.ajax.reload(null, false); }; swh.webapp.showModalConfirm( 'Accept origin save request ?', 'Are you sure to accept this origin save request ?', acceptOriginSaveRequestCallback); } } const rejectModalHtml = `
`; export function rejectOriginSaveRequest() { const selectedRow = pendingSaveRequestsTable.row('.selected'); const rowData = selectedRow.data(); if (selectedRow.length) { const rejectOriginSaveRequestCallback = async() => { $('#swh-web-modal-html').modal('hide'); const rejectSaveRequestUrl = Urls.admin_origin_save_request_reject( rowData['visit_type'], rowData['origin_url']); await csrfPost(rejectSaveRequestUrl, {}, JSON.stringify({note: $('#swh-rejection-text').val()})); pendingSaveRequestsTable.ajax.reload(null, false); }; let currentRejectionReason = 'custom'; const rejectionTexts = {}; swh.webapp.showModalHtml('Reject origin save request ?', rejectModalHtml); $('#swh-rejection-reason').on('change', (event) => { // backup current textarea value rejectionTexts[currentRejectionReason] = $('#swh-rejection-text').val(); currentRejectionReason = event.target.value; let newRejectionText = ''; if (rejectionTexts.hasOwnProperty(currentRejectionReason)) { // restore previous textarea value newRejectionText = rejectionTexts[currentRejectionReason]; } else { // fill textarea with default text according to rejection type if (currentRejectionReason === 'invalid-origin') { newRejectionText = `The origin with URL ${rowData['origin_url']} is not ` + `a link to a ${rowData['visit_type']} repository.`; } else if (currentRejectionReason === 'invalid-origin-type') { newRejectionText = `The origin with URL ${rowData['origin_url']} is not ` + `of type ${rowData['visit_type']}.`; } else if (currentRejectionReason === 'origin-not-found') { newRejectionText = `The origin with URL ${rowData['origin_url']} cannot be found.`; } } $('#swh-rejection-text').val(newRejectionText); }); $('#swh-rejection-form').on('submit', (event) => { event.preventDefault(); event.stopPropagation(); // ensure confirmation modal will be displayed above the html modal $('#swh-web-modal-html').css('z-index', 4000); swh.webapp.showModalConfirm( 'Reject origin save request ?', 'Are you sure to reject this origin save request ?', rejectOriginSaveRequestCallback); }); } } function removeOriginSaveRequest(requestTable) { const selectedRow = requestTable.row('.selected'); if (selectedRow.length) { const requestId = selectedRow.data()['id']; const removeOriginSaveRequestCallback = async() => { const removeSaveRequestUrl = Urls.admin_origin_save_request_remove(requestId); await csrfPost(removeSaveRequestUrl); requestTable.ajax.reload(null, false); }; swh.webapp.showModalConfirm( 'Remove origin save request ?', 'Are you sure to remove this origin save request ?', removeOriginSaveRequestCallback); } } export function removePendingOriginSaveRequest() { removeOriginSaveRequest(pendingSaveRequestsTable); } export function removeAcceptedOriginSaveRequest() { removeOriginSaveRequest(acceptedSaveRequestsTable); } export function removeRejectedOriginSaveRequest() { removeOriginSaveRequest(rejectedSaveRequestsTable); } diff --git a/cypress/integration/admin.spec.js b/cypress/integration/admin.spec.js index 321d38ec..5428a047 100644 --- a/cypress/integration/admin.spec.js +++ b/cypress/integration/admin.spec.js @@ -1,295 +1,295 @@ /** - * Copyright (C) 2019-2021 The Software Heritage developers + * Copyright (C) 2019-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 */ const $ = Cypress.$; -const defaultRedirect = '/admin/origin/save/'; +const defaultRedirect = '/admin/origin/save/requests/'; let url; function logout() { cy.contains('a', 'logout') .click(); } describe('Test Admin Login/logout', function() { before(function() { url = this.Urls.admin(); }); it('should redirect to default page', function() { cy.visit(url) .get('input[name="username"]') .type('admin') .get('input[name="password"]') .type('admin') .get('.container form') .submit(); cy.location('pathname') .should('be.equal', defaultRedirect); logout(); }); it('should display admin-origin-save and deposit in sidebar', function() { cy.adminLogin(); cy.visit(url); - cy.get(`.sidebar a[href="${this.Urls.admin_origin_save()}"]`) + cy.get(`.sidebar a[href="${this.Urls.admin_origin_save_requests()}"]`) .should('be.visible'); cy.get(`.sidebar a[href="${this.Urls.admin_deposit()}"]`) .should('be.visible'); logout(); }); it('should display username on top-right', function() { cy.adminLogin(); cy.visit(url); cy.get('.swh-position-right') .should('contain', 'admin'); logout(); }); it('should get info about a user logged in from javascript', function() { cy.window().then(win => { expect(win.swh.webapp.isUserLoggedIn()).to.be.false; }); cy.adminLogin(); cy.visit(url); cy.window().then(win => { expect(win.swh.webapp.isUserLoggedIn()).to.be.true; }); logout(); cy.visit(url); cy.window().then(win => { expect(win.swh.webapp.isUserLoggedIn()).to.be.false; }); }); it('should prevent unauthorized access after logout', function() { - cy.visit(this.Urls.admin_origin_save()) + cy.visit(this.Urls.admin_origin_save_requests()) .location('pathname') .should('be.equal', '/admin/login/'); cy.visit(this.Urls.admin_deposit()) .location('pathname') .should('be.equal', '/admin/login/'); }); it('should redirect to correct page after login', function() { // mock calls to deposit list api to avoid possible errors // while running the test cy.intercept(`${this.Urls.admin_deposit_list()}**`, { body: { data: [], recordsTotal: 0, recordsFiltered: 0, draw: 1 } }); cy.visit(this.Urls.admin_deposit()) .location('search') .should('contain', `next=${this.Urls.admin_deposit()}`); cy.adminLogin(); cy.visit(this.Urls.admin_deposit()); cy.location('pathname') .should('be.equal', this.Urls.admin_deposit()); logout(); }); }); const existingRowToSelect = 'https://bitbucket.org/'; const originUrlListTestData = [ { listType: 'authorized', originToAdd: 'git://git.archlinux.org/', originToRemove: 'https://github.com/' }, { listType: 'unauthorized', originToAdd: 'https://random.org', originToRemove: 'https://gitlab.com' } ]; const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1); describe('Test Admin Origin Save Urls Filtering', function() { beforeEach(function() { cy.adminLogin(); - cy.visit(this.Urls.admin_origin_save()); + cy.visit(this.Urls.admin_origin_save_requests()); cy.contains('a', 'Origin urls filtering') .click() .wait(500); }); it(`should select or unselect a table row by clicking on it`, function() { cy.contains(`#swh-authorized-origin-urls tr`, existingRowToSelect) .click() .should('have.class', 'selected') .click() .should('not.have.class', 'selected'); }); originUrlListTestData.forEach(testData => { it(`should add a new origin url prefix in the ${testData.listType} list`, function() { const tabName = capitalize(testData.listType) + ' urls'; cy.contains('a', tabName) .click() .wait(500); cy.get(`#swh-${testData.listType}-origin-urls tr`).each(elt => { if ($(elt).text() === testData.originToAdd) { cy.get(elt).click(); cy.get(`#swh-remove-${testData.listType}-origin-url`).click(); } }); cy.get(`#swh-${testData.listType}-url-prefix`) .type(testData.originToAdd); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToAdd) .should('be.visible'); cy.contains('.alert-success', `The origin url prefix has been successfully added in the ${testData.listType} list.`) .should('be.visible'); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.contains('.alert-warning', `The provided origin url prefix is already registered in the ${testData.listType} list.`) .should('be.visible'); }); it(`should remove an origin url prefix from the ${testData.listType} list`, function() { const tabName = capitalize(testData.listType) + ' urls'; cy.contains('a', tabName) .click(); let originUrlMissing = true; cy.get(`#swh-${testData.listType}-origin-urls tr`).each(elt => { if ($(elt).text() === testData.originToRemove) { originUrlMissing = false; } }); if (originUrlMissing) { cy.get(`#swh-${testData.listType}-url-prefix`) .type(testData.originToRemove); cy.get(`#swh-add-${testData.listType}-origin-url`) .click(); cy.get('.alert-dismissible button').click(); } cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToRemove) .click(); cy.get(`#swh-remove-${testData.listType}-origin-url`).click(); cy.contains(`#swh-${testData.listType}-origin-urls tr`, testData.originToRemove) .should('not.exist'); }); }); }); describe('Test Admin Origin Save', function() { it(`should reject a save code now request with note`, function() { const originUrl = `https://example.org/${Date.now()}`; const rejectionNote = 'The provided URL does not target a git repository.'; // anonymous user creates a request put in pending state cy.visit(this.Urls.origin_save()); cy.get('#swh-input-origin-url') .type(originUrl); cy.get('#swh-input-origin-save-submit') .click(); // admin user logs in and visits save code now admin page cy.adminLogin(); - cy.visit(this.Urls.admin_origin_save()); + cy.visit(this.Urls.admin_origin_save_requests()); // admin rejects the save request and adds a rejection note cy.contains('#swh-origin-save-pending-requests', originUrl) .click(); cy.get('#swh-reject-save-origin-request') .click(); cy.get('#swh-rejection-text') .then(textarea => { textarea.val(rejectionNote); }); cy.get('#swh-rejection-submit') .click(); cy.get('#swh-web-modal-confirm-ok-btn') .click(); // checks rejection note has been saved to swh-web database cy.request(this.Urls.api_1_save_origin('git', originUrl)) .then(response => { expect(response.body[0]['note']).to.equal(rejectionNote); }); // check rejection note is displayed by clicking on the info icon // in requests table from public save code now page cy.visit(this.Urls.origin_save()); cy.get('#swh-origin-save-requests-list-tab') .click(); cy.contains('#swh-origin-save-requests tr', originUrl); cy.get('.swh-save-request-info') .eq(0) .click(); cy.get('.popover-body') .should('have.text', rejectionNote); // remove rejected request from swh-web database to avoid side effects // in tests located in origin-save.spec.js - cy.visit(this.Urls.admin_origin_save()); + cy.visit(this.Urls.admin_origin_save_requests()); cy.get('#swh-save-requests-rejected-tab') .click(); cy.contains('#swh-origin-save-rejected-requests', originUrl) .click(); cy.get('#swh-remove-rejected-save-origin-request') .click(); cy.get('#swh-web-modal-confirm-ok-btn') .click(); }); }); diff --git a/swh/web/admin/origin_save.py b/swh/web/admin/origin_save.py index af00e899..3b722e5f 100644 --- a/swh/web/admin/origin_save.py +++ b/swh/web/admin/origin_save.py @@ -1,214 +1,220 @@ # Copyright (C) 2018-2019 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 json from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required from django.core.exceptions import ObjectDoesNotExist from django.core.paginator import Paginator from django.http import HttpResponse, JsonResponse from django.shortcuts import render from django.views.decorators.http import require_POST from swh.web.admin.adminurls import admin_route from swh.web.common.models import ( SaveAuthorizedOrigin, SaveOriginRequest, SaveUnauthorizedOrigin, ) from swh.web.common.origin_save import ( SAVE_REQUEST_PENDING, SAVE_REQUEST_REJECTED, create_save_origin_request, ) -@admin_route(r"origin/save/", view_name="admin-origin-save") +@admin_route(r"origin/save/requests/", view_name="admin-origin-save-requests") @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) -def _admin_origin_save(request): - return render(request, "admin/origin-save.html") +def _admin_origin_save_requests(request): + return render(request, "admin/origin-save/requests.html") + + +@admin_route(r"origin/save/filters/", view_name="admin-origin-save-filters") +@staff_member_required(view_func=None, login_url=settings.LOGIN_URL) +def _admin_origin_save_filters(request): + return render(request, "admin/origin-save/filters.html") def _datatables_origin_urls_response(request, urls_query_set): search_value = request.GET["search[value]"] if search_value: urls_query_set = urls_query_set.filter(url__icontains=search_value) column_order = request.GET["order[0][column]"] field_order = request.GET["columns[%s][name]" % column_order] order_dir = request.GET["order[0][dir]"] if order_dir == "desc": field_order = "-" + field_order urls_query_set = urls_query_set.order_by(field_order) table_data = {} table_data["draw"] = int(request.GET["draw"]) table_data["recordsTotal"] = urls_query_set.count() table_data["recordsFiltered"] = urls_query_set.count() length = int(request.GET["length"]) page = int(request.GET["start"]) / length + 1 paginator = Paginator(urls_query_set, length) urls_query_set = paginator.page(page).object_list table_data["data"] = [{"url": u.url} for u in urls_query_set] return JsonResponse(table_data) @admin_route( r"origin/save/authorized_urls/list/", view_name="admin-origin-save-authorized-urls-list", ) @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_authorized_urls_list(request): authorized_urls = SaveAuthorizedOrigin.objects.all() return _datatables_origin_urls_response(request, authorized_urls) @admin_route( r"origin/save/authorized_urls/add/(?P.+)/", view_name="admin-origin-save-add-authorized-url", ) @require_POST @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_add_authorized_url(request, origin_url): try: SaveAuthorizedOrigin.objects.get(url=origin_url) except ObjectDoesNotExist: # add the new authorized url SaveAuthorizedOrigin.objects.create(url=origin_url) # check if pending save requests with that url prefix exist pending_save_requests = SaveOriginRequest.objects.filter( origin_url__startswith=origin_url, status=SAVE_REQUEST_PENDING ) # create origin save tasks for previously pending requests for psr in pending_save_requests: create_save_origin_request(psr.visit_type, psr.origin_url) status_code = 200 else: status_code = 400 return HttpResponse(status=status_code) @admin_route( r"origin/save/authorized_urls/remove/(?P.+)/", view_name="admin-origin-save-remove-authorized-url", ) @require_POST @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_remove_authorized_url(request, origin_url): try: entry = SaveAuthorizedOrigin.objects.get(url=origin_url) except ObjectDoesNotExist: status_code = 404 else: entry.delete() status_code = 200 return HttpResponse(status=status_code) @admin_route( r"origin/save/unauthorized_urls/list/", view_name="admin-origin-save-unauthorized-urls-list", ) @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_unauthorized_urls_list(request): unauthorized_urls = SaveUnauthorizedOrigin.objects.all() return _datatables_origin_urls_response(request, unauthorized_urls) @admin_route( r"origin/save/unauthorized_urls/add/(?P.+)/", view_name="admin-origin-save-add-unauthorized-url", ) @require_POST @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_add_unauthorized_url(request, origin_url): try: SaveUnauthorizedOrigin.objects.get(url=origin_url) except ObjectDoesNotExist: SaveUnauthorizedOrigin.objects.create(url=origin_url) # check if pending save requests with that url prefix exist pending_save_requests = SaveOriginRequest.objects.filter( origin_url__startswith=origin_url, status=SAVE_REQUEST_PENDING ) # mark pending requests as rejected for psr in pending_save_requests: psr.status = SAVE_REQUEST_REJECTED psr.save() status_code = 200 else: status_code = 400 return HttpResponse(status=status_code) @admin_route( r"origin/save/unauthorized_urls/remove/(?P.+)/", view_name="admin-origin-save-remove-unauthorized-url", ) @require_POST @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_remove_unauthorized_url(request, origin_url): try: entry = SaveUnauthorizedOrigin.objects.get(url=origin_url) except ObjectDoesNotExist: status_code = 404 else: entry.delete() status_code = 200 return HttpResponse(status=status_code) @admin_route( r"origin/save/request/accept/(?P.+)/url/(?P.+)/", view_name="admin-origin-save-request-accept", ) @require_POST @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_request_accept(request, visit_type, origin_url): try: SaveAuthorizedOrigin.objects.get(url=origin_url) except ObjectDoesNotExist: SaveAuthorizedOrigin.objects.create(url=origin_url) create_save_origin_request(visit_type, origin_url) return HttpResponse(status=200) @admin_route( r"origin/save/request/reject/(?P.+)/url/(?P.+)/", view_name="admin-origin-save-request-reject", ) @require_POST @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_request_reject(request, visit_type, origin_url): try: sor = SaveOriginRequest.objects.get( visit_type=visit_type, origin_url=origin_url, status=SAVE_REQUEST_PENDING ) except ObjectDoesNotExist: status_code = 404 else: status_code = 200 sor.status = SAVE_REQUEST_REJECTED sor.note = json.loads(request.body).get("note") sor.save() return HttpResponse(status=status_code) @admin_route( r"origin/save/request/remove/(?P.+)/", view_name="admin-origin-save-request-remove", ) @require_POST @staff_member_required(view_func=None, login_url=settings.LOGIN_URL) def _admin_origin_save_request_remove(request, sor_id): try: entry = SaveOriginRequest.objects.get(id=sor_id) except ObjectDoesNotExist: status_code = 404 else: entry.delete() status_code = 200 return HttpResponse(status=status_code) diff --git a/swh/web/admin/urls.py b/swh/web/admin/urls.py index 57d799ae..27014677 100644 --- a/swh/web/admin/urls.py +++ b/swh/web/admin/urls.py @@ -1,29 +1,29 @@ # 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 from django.conf.urls import url from django.contrib.auth.views import LoginView from django.shortcuts import redirect from swh.web.admin.adminurls import AdminUrls import swh.web.admin.deposit # noqa import swh.web.admin.mailmap # noqa import swh.web.admin.origin_save # noqa from swh.web.config import is_feature_enabled if is_feature_enabled("add_forge_now"): import swh.web.admin.add_forge_now # noqa def _admin_default_view(request): - return redirect("admin-origin-save") + return redirect("admin-origin-save-requests") urlpatterns = [ url(r"^$", _admin_default_view, name="admin"), url(r"^login/$", LoginView.as_view(template_name="login.html"), name="login"), ] urlpatterns += AdminUrls.get_url_patterns() diff --git a/swh/web/templates/admin/origin-save/common.html b/swh/web/templates/admin/origin-save/common.html new file mode 100644 index 00000000..b284771d --- /dev/null +++ b/swh/web/templates/admin/origin-save/common.html @@ -0,0 +1,41 @@ +{% extends "layout.html" %} + +{% comment %} +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 +{% endcomment %} + +{% load swh_templatetags %} +{% load render_bundle from webpack_loader %} + +{% block header %} +{{ block.super }} +{% render_bundle 'admin' %} +{% render_bundle 'save' %} +{% endblock %} + +{% block title %} Save origin administration {% endblock %} + +{% block navbar-content %} +

Save origin administration

+{% endblock %} + +{% block content %} + + + +
+ {% block tab_content %} + {% endblock %} +
+ + +{% endblock %} diff --git a/swh/web/templates/admin/origin-save/filters.html b/swh/web/templates/admin/origin-save/filters.html new file mode 100644 index 00000000..eb3fc960 --- /dev/null +++ b/swh/web/templates/admin/origin-save/filters.html @@ -0,0 +1,76 @@ +{% extends "./common.html" %} + +{% comment %} +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 +{% endcomment %} + + +{% block tab_content %} +
+ + +
+
+ + + + + + +
Url
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ +
+ + + + + + +
Url
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+{% endblock %} diff --git a/swh/web/templates/admin/origin-save.html b/swh/web/templates/admin/origin-save/requests.html similarity index 51% rename from swh/web/templates/admin/origin-save.html rename to swh/web/templates/admin/origin-save/requests.html index 832d5470..8ea2dad1 100644 --- a/swh/web/templates/admin/origin-save.html +++ b/swh/web/templates/admin/origin-save/requests.html @@ -1,186 +1,93 @@ -{% extends "layout.html" %} +{% extends "./common.html" %} {% comment %} -Copyright (C) 2018-2021 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 {% endcomment %} -{% load swh_templatetags %} -{% load render_bundle from webpack_loader %} - -{% block header %} -{{ block.super }} -{% render_bundle 'admin' %} -{% render_bundle 'save' %} -{% endblock %} - -{% block title %} Save origin administration {% endblock %} - -{% block navbar-content %} -

Save origin administration

-{% endblock %} - -{% block content %} - - - -
+{% block tab_content %}
Date Type Url
Date Type Url Status Info
Date Type Url Info
- -
- - -
-
- - - - - - -
Url
-
-
-
- -
- -
-
-
-
- -
-
-
-
-
- -
- - - - - - -
Url
-
-
-
- -
- -
-
-
-
- -
-
-
-
-
-
-
-
- - {% endblock %} diff --git a/swh/web/templates/layout.html b/swh/web/templates/layout.html index d74886ff..9f02ad31 100644 --- a/swh/web/templates/layout.html +++ b/swh/web/templates/layout.html @@ -1,315 +1,315 @@ {% comment %} Copyright (C) 2015-2021 The Software Heritage developers See the AUTHORS file at the top-level directory of this distribution License: GNU Affero General Public License version 3, or any later version See top-level LICENSE file for more information {% endcomment %} {% load js_reverse %} {% load static %} {% load render_bundle from webpack_loader %} {% load swh_templatetags %} {% block title %}{% endblock %} {% render_bundle 'vendors' %} {% render_bundle 'webapp' %} {% render_bundle 'guided_tour' %} {{ request.user.is_authenticated|json_script:"swh_user_logged_in" }} {% block header %}{% endblock %} {% if not swh_web_dev and not swh_web_staging %} {% endif %}
{% comment %} {% endcomment %}
{% if swh_web_staging %}
Staging
v{{ swh_web_version }}
{% elif swh_web_dev %}
Development
v{{ swh_web_version|split:"+"|first }}
{% endif %} {% block content %}{% endblock %}
{% include "includes/global-modals.html" %}
back to top