diff --git a/swh/web/admin/deposit.py b/swh/web/admin/deposit.py new file mode 100644 index 00000000..1f88f3a0 --- /dev/null +++ b/swh/web/admin/deposit.py @@ -0,0 +1,85 @@ +# 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 json +import requests + +from django.core.cache import cache +from django.conf import settings +from django.contrib.admin.views.decorators import staff_member_required +from django.core.paginator import Paginator +from django.http import HttpResponse +from django.shortcuts import render + +from requests.auth import HTTPBasicAuth + +from swh.web.admin.adminurls import admin_route +from swh.web.config import get_config + +config = get_config()['deposit'] + + +@admin_route(r'deposit/', view_name='admin-deposit') +@staff_member_required(login_url=settings.LOGIN_URL) +def _admin_origin_save(request): + return render(request, 'admin/deposit.html') + + +@admin_route(r'deposit/list/', + view_name='admin-deposit-list') +@staff_member_required(login_url=settings.LOGIN_URL) +def _admin_deposit_list(request): + table_data = {} + table_data['draw'] = int(request.GET['draw']) + deposits_list_url = config['private_api_url'] + 'deposits' + deposits_list_auth = HTTPBasicAuth(config['private_api_user'], + config['private_api_password']) + + try: + nb_deposits = requests.get('%s?page_size=1' % deposits_list_url, + auth=deposits_list_auth).json()['count'] + + deposits_data = cache.get('swh-deposit-list') + if not deposits_data or deposits_data['count'] != nb_deposits: + deposits_data = requests.get('%s?page_size=%s' % + (deposits_list_url, nb_deposits), + auth=deposits_list_auth).json() + cache.set('swh-deposit-list', deposits_data) + + deposits = deposits_data['results'] + + search_value = request.GET['search[value]'] + if search_value: + deposits = \ + [d for d in deposits + if any(search_value.lower() in val + for val in [str(v).lower() for v in d.values()])] + + column_order = request.GET['order[0][column]'] + field_order = request.GET['columns[%s][name]' % column_order] + order_dir = request.GET['order[0][dir]'] + + deposits = sorted(deposits, key=lambda d: d[field_order] or '') + if order_dir == 'desc': + deposits = list(reversed(deposits)) + + length = int(request.GET['length']) + page = int(request.GET['start']) / length + 1 + paginator = Paginator(deposits, length) + data = paginator.page(page).object_list + table_data['recordsTotal'] = deposits_data['count'] + table_data['recordsFiltered'] = len(deposits) + table_data['data'] = [{'id': d['id'], + 'external_id': d['external_id'], + 'reception_date': d['reception_date'], + 'status': d['status'], + 'status_detail': d['status_detail'], + 'swh_id': d['swh_id'] + } for d in data] + except Exception: + table_data['error'] = 'An error occurred while retrieving the list of deposits !' # noqa + + return HttpResponse(json.dumps(table_data), + content_type='application/json') diff --git a/swh/web/admin/urls.py b/swh/web/admin/urls.py index f68613a5..1f5a2d7a 100644 --- a/swh/web/admin/urls.py +++ b/swh/web/admin/urls.py @@ -1,25 +1,26 @@ # 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 from django.conf.urls import url from django.contrib.auth import views as auth_views from django.shortcuts import redirect from swh.web.admin.adminurls import AdminUrls import swh.web.admin.origin_save # noqa +import swh.web.admin.deposit # noqa def admin_default_view(request): return redirect('admin-origin-save') urlpatterns = [url(r'^$', admin_default_view, name='admin'), url(r'^login/$', auth_views.login, {'template_name': 'login.html'}, name='login'), url(r'^logout/$', auth_views.logout, {'template_name': 'logout.html'}, name='logout')] urlpatterns += AdminUrls.get_url_patterns() diff --git a/swh/web/assets/src/bundles/admin/deposit.js b/swh/web/assets/src/bundles/admin/deposit.js new file mode 100644 index 00000000..a899964e --- /dev/null +++ b/swh/web/assets/src/bundles/admin/deposit.js @@ -0,0 +1,79 @@ +export function initDepositAdmin() { + 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, + ajax: Urls.admin_deposit_list(), + columns: [ + { + data: 'id', + name: 'id' + }, + { + data: 'external_id', + name: 'external_id', + render: (data, type, row) => { + if (type === 'display') { + if (data && data.startsWith('hal')) { + return `${data}`; + } + } + return data; + } + }, + { + data: 'reception_date', + name: 'reception_date', + render: (data, type, row) => { + if (type === 'display') { + let date = new Date(data); + return date.toLocaleString(); + } + return data; + } + }, + { + data: 'status', + name: 'status' + }, + { + 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 `
${text}
`; + } + return data; + }, + orderable: false + }, + { + data: 'swh_id', + name: 'swh_id', + render: (data, type, row) => { + if (type === 'display') { + if (data && data.startsWith('swh')) { + let browseUrl = Urls.browse_swh_id(data); + return `${data}`; + } + } + return data; + } + } + ], + scrollY: '50vh', + scrollCollapse: true, + order: [[0, 'desc']] + }); + depositsTable.draw(); + }); +} diff --git a/swh/web/assets/src/bundles/admin/index.js b/swh/web/assets/src/bundles/admin/index.js index c2ab2bbf..6910bd3a 100644 --- a/swh/web/assets/src/bundles/admin/index.js +++ b/swh/web/assets/src/bundles/admin/index.js @@ -1,8 +1,9 @@ /** * 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 */ +export * from './deposit'; export * from './origin-save'; diff --git a/swh/web/config.py b/swh/web/config.py index 596a60ee..e4052284 100644 --- a/swh/web/config.py +++ b/swh/web/config.py @@ -1,123 +1,128 @@ # Copyright (C) 2017-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 from swh.core import config from swh.storage import get_storage from swh.indexer.storage import get_indexer_storage from swh.vault.api.client import RemoteVaultClient from swh.scheduler import get_scheduler DEFAULT_CONFIG = { 'allowed_hosts': ('list', []), 'storage': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://127.0.0.1:5002/', 'timeout': 10, }, }), 'indexer_storage': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://127.0.0.1:5007/', 'timeout': 1, } }), 'vault': ('string', 'http://127.0.0.1:5005/'), 'log_dir': ('string', '/tmp/swh/log'), 'debug': ('bool', False), 'host': ('string', '127.0.0.1'), 'port': ('int', 5004), 'secret_key': ('string', 'development key'), # do not display code highlighting for content > 1MB 'content_display_max_size': ('int', 1024 * 1024), 'throttling': ('dict', { 'cache_uri': None, # production: memcached as cache (127.0.0.1:11211) # development: in-memory cache so None 'scopes': { 'swh_api': { 'limiter_rate': { 'default': '120/h' }, 'exempted_networks': ['127.0.0.0/8'] }, 'swh_vault_cooking': { 'limiter_rate': { 'default': '120/h', 'GET': '60/m' }, 'exempted_networks': ['127.0.0.0/8'] }, 'swh_save_origin': { 'limiter_rate': { 'default': '120/h', 'POST': '10/h' }, 'exempted_networks': ['127.0.0.0/8'] } } }), 'scheduler': ('dict', { 'cls': 'remote', 'args': { 'url': 'http://localhost:5008/' } }), 'grecaptcha': ('dict', { 'validation_url': 'https://www.google.com/recaptcha/api/siteverify', 'site_key': '', 'private_key': '' }), - 'production_db': ('string', '/var/lib/swh/web.sqlite3') + 'production_db': ('string', '/var/lib/swh/web.sqlite3'), + 'deposit': ('dict', { + 'private_api_url': 'https://deposit.softwareheritage.org/1/private/', + 'private_api_user': 'swhworker', + 'private_api_password': '' + }) } swhweb_config = {} def get_config(config_file='webapp/webapp'): """Read the configuration file `config_file`, update the app with parameters (secret_key, conf) and return the parsed configuration as a dict. If no configuration file is provided, return a default configuration.""" if not swhweb_config: cfg = config.load_named_config(config_file, DEFAULT_CONFIG) swhweb_config.update(cfg) config.prepare_folders(swhweb_config, 'log_dir') swhweb_config['storage'] = get_storage(**swhweb_config['storage']) swhweb_config['vault'] = RemoteVaultClient(swhweb_config['vault']) swhweb_config['indexer_storage'] = \ get_indexer_storage(**swhweb_config['indexer_storage']) swhweb_config['scheduler'] = get_scheduler(**swhweb_config['scheduler']) # noqa return swhweb_config def storage(): """Return the current application's SWH storage. """ return get_config()['storage'] def vault(): """Return the current application's SWH vault. """ return get_config()['vault'] def indexer_storage(): """Return the current application's SWH indexer storage. """ return get_config()['indexer_storage'] def scheduler(): """Return the current application's SWH scheduler. """ return get_config()['scheduler'] diff --git a/swh/web/templates/admin/deposit.html b/swh/web/templates/admin/deposit.html new file mode 100644 index 00000000..f65f53ad --- /dev/null +++ b/swh/web/templates/admin/deposit.html @@ -0,0 +1,45 @@ +{% extends "layout.html" %} + +{% comment %} +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 +{% endcomment %} + +{% load swh_templatetags %} +{% load render_bundle from webpack_loader %} + +{% block header %} +{{ block.super }} +{% render_bundle 'admin' %} +{% endblock %} + +{% block title %} Deposit administration {% endblock %} + +{% block navbar-content %} +

Deposit administration

+{% endblock %} + +{% block content %} +

+ The table below displays the whole list of software deposits into the archive + submitted through HAL. +

+ + + + + + + + + + + +
deposit idexternal idreception datestatusstatus detailswh id
+

+ +{% endblock content %} \ No newline at end of file diff --git a/swh/web/templates/layout.html b/swh/web/templates/layout.html index 981f3d79..80686e63 100644 --- a/swh/web/templates/layout.html +++ b/swh/web/templates/layout.html @@ -1,175 +1,181 @@ {% comment %} Copyright (C) 2015-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 {% endcomment %} {% load static %} {% load render_bundle from webpack_loader %} {% block title %}{% endblock %} {% render_bundle 'vendors' %} {% render_bundle 'webapp' %} {% block header %}{% endblock %}
{% block content %}{% endblock %}
{% include "includes/global-modals.html" %}
back to top