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 += `×tamp=${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 %}
+
+
+
+
+
+
+
+
+
+ Remove selected
+
+
+
+
+
+
+
+
+
+
+
+ Remove selected
+
+
+
+
+
+
+
+{% 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 %}
Accept selected
Reject selected
Remove selected
Date
Type
Url
Status
Info
-
-
-
-
-
-
-
-
-
-
- Remove selected
-
-
-
-
-
-
-
-
-
-
-
- Remove selected
-
-
-
-
-
-
-
-
-
-
{% 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 %}
{% block navbar-content %}{% endblock %}
{% if request.resolver_match.url_name != 'swh-web-homepage' and request.resolver_match.url_name != 'browse-search' %}
{% endif %}
{% 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" %}