diff --git a/seahub/api2/endpoints/admin/trash_libraries.py b/seahub/api2/endpoints/admin/trash_libraries.py index f46b404e2b..784b1d475e 100644 --- a/seahub/api2/endpoints/admin/trash_libraries.py +++ b/seahub/api2/endpoints/admin/trash_libraries.py @@ -18,6 +18,16 @@ from seahub.api2.utils import api_error logger = logging.getLogger(__name__) +def get_trash_repo_info(repo): + + result = {} + result['name'] = repo.repo_name + result['id'] = repo.repo_id + result['owner'] = repo.owner_id + result['delete_time'] = timestamp_to_isoformat_timestr(repo.del_time) + + return result + class AdminTrashLibraries(APIView): @@ -31,6 +41,8 @@ class AdminTrashLibraries(APIView): Permission checking: 1. only admin can perform this action. """ + + # list by owner search_owner = request.GET.get('owner', '') if search_owner: if not is_valid_username(search_owner): @@ -38,20 +50,45 @@ class AdminTrashLibraries(APIView): return api_error(status.HTTP_400_BAD_REQUEST, error_msg) repos = seafile_api.get_trash_repos_by_owner(search_owner) + + return_repos = [] + for repo in repos: + result = get_trash_repo_info(repo) + return_repos.append(result) + + return Response({"search_owner": search_owner, "repos": return_repos}) + + # list by page + try: + current_page = int(request.GET.get('page', '1')) + per_page = int(request.GET.get('per_page', '100')) + except ValueError: + current_page = 1 + per_page = 100 + + start = (current_page - 1) * per_page + limit = per_page + 1 + + repos_all = seafile_api.get_trash_repo_list(start, limit) + + if len(repos_all) > per_page: + repos_all = repos_all[:per_page] + has_next_page = True else: - repos = seafile_api.get_trash_repo_list(-1, -1) + has_next_page = False - return_repos = [] - for repo in repos: - result = {} - result['name'] = repo.repo_name - result['id'] = repo.repo_id - result['owner'] = repo.owner_id - result['delete_time'] = timestamp_to_isoformat_timestr(repo.del_time) + return_results = [] + for repo in repos_all: + repo_info = get_trash_repo_info(repo) + return_results.append(repo_info) - return_repos.append(result) + page_info = { + 'has_next_page': has_next_page, + 'current_page': current_page + } + + return Response({"page_info": page_info, "repos": return_results}) - return Response({"search_owner": search_owner, "repos": return_repos}) def delete(self, request, format=None): """ clean all deleted libraries(by owner) diff --git a/seahub/templates/js/sysadmin-templates.html b/seahub/templates/js/sysadmin-templates.html index 9148f7f39b..7d01369d12 100644 --- a/seahub/templates/js/sysadmin-templates.html +++ b/seahub/templates/js/sysadmin-templates.html @@ -64,7 +64,7 @@ <% } %> <% if (option == 'trash') { %> -
+
<% } %> @@ -164,8 +164,8 @@

{% trans "No connected devices" %}

- {% trans "Previous" %} - {% trans "Next" %} + +
@@ -298,8 +298,8 @@
- {% trans "Previous" %} - {% trans "Next" %} + +

{% trans "No libraries" %}

@@ -400,6 +400,10 @@ +
+ + +

{% trans "No library deleted yet" %}

diff --git a/seahub/templates/snippets/admin_paginator.html b/seahub/templates/snippets/admin_paginator.html index 1e824870e3..1c38e9fb3a 100644 --- a/seahub/templates/snippets/admin_paginator.html +++ b/seahub/templates/snippets/admin_paginator.html @@ -1,4 +1,5 @@ -{% load i18n%}
+{% load i18n%} +
{% if current_page != 1 %} {% trans "Previous" %} {% endif %} diff --git a/seahub/urls.py b/seahub/urls.py index 17f7bfba5f..b901885b09 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -230,6 +230,7 @@ urlpatterns = patterns( url(r'^sysadmin/#system-lib/$', fake_view, name='sys_list_system'), url(r'^sysadmin/#trash-libs/$', fake_view, name='sys_repo_trash'), url(r'^sysadmin/#search-libs/$', fake_view, name='sys_repo_search'), + url(r'^sysadmin/#search-trash-libs/$', fake_view, name='sys_trash_repo_search'), url(r'^sys/seafadmin/transfer/$', sys_repo_transfer, name='sys_repo_transfer'), url(r'^sys/seafadmin/delete/(?P[-0-9a-f]{36})/$', sys_repo_delete, name='sys_repo_delete'), url(r'^sys/useradmin/$', sys_user_admin, name='sys_useradmin'), diff --git a/static/scripts/sysadmin-app/collection/search-trash-repos.js b/static/scripts/sysadmin-app/collection/search-trash-repos.js new file mode 100644 index 0000000000..3bb161302d --- /dev/null +++ b/static/scripts/sysadmin-app/collection/search-trash-repos.js @@ -0,0 +1,24 @@ +define([ + 'underscore', + 'backbone', + 'common', + 'sysadmin-app/models/trash-repo' +], function(_, Backbone, Common, TrashRepoModel) { + 'use strict'; + + var TrashRepoCollection = Backbone.Collection.extend({ + model: TrashRepoModel, + + url: function () { + return Common.getUrl({name: 'admin-trash-libraries'}); + }, + + parse: function(data) { + this.search_owner = data.search_owner; + return data.repos; + } + + }); + + return TrashRepoCollection; +}); diff --git a/static/scripts/sysadmin-app/collection/trash-repos.js b/static/scripts/sysadmin-app/collection/trash-repos.js index 3bb161302d..f5553bdf97 100644 --- a/static/scripts/sysadmin-app/collection/trash-repos.js +++ b/static/scripts/sysadmin-app/collection/trash-repos.js @@ -1,20 +1,25 @@ define([ 'underscore', - 'backbone', + 'backbone.paginator', 'common', 'sysadmin-app/models/trash-repo' -], function(_, Backbone, Common, TrashRepoModel) { +], function(_, BackbonePaginator, Common, TrashRepoModel) { 'use strict'; - var TrashRepoCollection = Backbone.Collection.extend({ + var TrashRepoCollection = Backbone.PageableCollection.extend({ model: TrashRepoModel, url: function () { return Common.getUrl({name: 'admin-trash-libraries'}); }, - parse: function(data) { - this.search_owner = data.search_owner; + state: {pageSize: 100}, + + parseState: function(data) { + return data.page_info; // {'has_next_page': has_next_page, 'current_page': current_pag + }, + + parseRecords: function(data) { return data.repos; } diff --git a/static/scripts/sysadmin-app/router.js b/static/scripts/sysadmin-app/router.js index f93e1a5dae..3416f1d219 100644 --- a/static/scripts/sysadmin-app/router.js +++ b/static/scripts/sysadmin-app/router.js @@ -12,12 +12,13 @@ define([ 'sysadmin-app/views/search-repos', 'sysadmin-app/views/system-repo', 'sysadmin-app/views/trash-repos', + 'sysadmin-app/views/search-trash-repos', 'sysadmin-app/views/dir', 'app/views/account' ], function($, Backbone, Common, SideNavView, DashboardView, DesktopDevicesView, MobileDevicesView, DeviceErrorsView, - ReposView, SearchReposView, SystemReposView, TrashReposView, DirView, - AccountView) { + ReposView, SearchReposView, SystemReposView, TrashReposView, + SearchTrashReposView, DirView, AccountView) { "use strict"; @@ -32,6 +33,7 @@ define([ 'search-libs/': 'showSearchLibraries', 'system-lib/': 'showSystemLibrary', 'trash-libs/': 'showTrashLibraries', + 'search-trash-libs/': 'showSearchTrashLibraries', 'libs/:repo_id(/*path)': 'showLibraryDir', // Default '*actions': 'showDashboard' @@ -57,6 +59,7 @@ define([ this.searchReposView = new SearchReposView(); this.systemReposView = new SystemReposView(); this.trashReposView = new TrashReposView(); + this.searchTrashReposView = new SearchTrashReposView(); this.dirView = new DirView(); app.ui.accountView = this.accountView = new AccountView(); @@ -152,14 +155,26 @@ define([ this.systemReposView.show(); }, + // show trash libs by page showTrashLibraries: function() { + // url_match: null or an array + var url_match = location.href.match(/.*?page=(\d+)/); + var page = url_match ? url_match[1] : 1; // 1: default + + this.switchCurrentView(this.trashReposView); + this.sideNavView.setCurTab('libraries', {'option': 'trash'}); + this.trashReposView.show({'page': page}); + }, + + // search trash libs by owner + showSearchTrashLibraries: function() { // url_match: null or an array var url_match = location.href.match(/.*?name=(.*)/); // search by owner var owner = url_match ? url_match[1] : ''; - this.switchCurrentView(this.trashReposView); + this.switchCurrentView(this.searchTrashReposView); this.sideNavView.setCurTab('libraries', {'option': 'trash'}); - this.trashReposView.show({'owner': decodeURIComponent(owner)}); + this.searchTrashReposView.show({'owner': decodeURIComponent(owner)}); } }); diff --git a/static/scripts/sysadmin-app/views/search-trash-repos.js b/static/scripts/sysadmin-app/views/search-trash-repos.js new file mode 100644 index 0000000000..53acfc106b --- /dev/null +++ b/static/scripts/sysadmin-app/views/search-trash-repos.js @@ -0,0 +1,147 @@ +define([ + 'jquery', + 'underscore', + 'backbone', + 'common', + 'moment', + 'sysadmin-app/views/trash-repo', + 'sysadmin-app/collection/search-trash-repos' +], function($, _, Backbone, Common, Moment, TrashRepoView, + TrashRepoCollection) { + 'use strict'; + + var TrashReposView = Backbone.View.extend({ + + id: 'trash-libraries', + + tabNavTemplate: _.template($("#libraries-tabnav-tmpl").html()), + template: _.template($("#trash-libraries-tmpl").html()), + + initialize: function() { + this.trashRepoCollection = new TrashRepoCollection(); + this.listenTo(this.trashRepoCollection, 'add', this.addOne); + this.listenTo(this.trashRepoCollection, 'reset', this.reset); + + this.render(); + }, + + render: function() { + this.$el.html(this.tabNavTemplate({'cur_tab': 'trash'}) + this.template()); + + this.$tip = this.$('.tip'); + this.$table = this.$('table'); + this.$tableBody = $('tbody', this.$table); + this.$loadingTip = this.$('.loading-tip'); + this.$emptyTip = this.$('.empty-tips'); + this.$cleanBtn = this.$('.js-clean'); + }, + + events: { + 'click .js-clean': 'cleanTrashLibraries' + }, + + cleanTrashLibraries: function() { + var _this = this; + var owner = this.trashRepoCollection.search_owner; + var popupTitle = gettext("Delete Library By Owner"); + var popupContent = gettext("Are you sure you want to delete all %s's libraries?").replace('%s', '' + Common.HTMLescape(owner) + ''); + var yesCallback = function() { + $.ajax({ + url: Common.getUrl({'name':'admin-trash-libraries'}), + type: 'DELETE', + data: {'owner': owner}, + beforeSend: Common.prepareCSRFToken, + dataType: 'json', + success: function() { + _this.$cleanBtn.hide(); + _this.$tip.hide(); + _this.$table.hide(); + _this.$emptyTip.show(); + Common.feedback(gettext("Success"), 'success'); + }, + error: function(xhr, textStatus, errorThrown) { + Common.ajaxErrorHandler(xhr, textStatus, errorThrown); + }, + complete: function() { + $.modal.close(); + } + }); + }; + Common.showConfirm(popupTitle, popupContent, yesCallback); + }, + + initPage: function() { + this.$tip.hide(); + this.$table.hide(); + this.$tableBody.empty(); + this.$loadingTip.show(); + this.$emptyTip.hide(); + this.$cleanBtn.hide(); + }, + + hide: function() { + this.$el.detach(); + this.attached = false; + }, + + show: function(option) { + this.option = option; + if (!this.attached) { + this.attached = true; + $("#right-panel").html(this.$el); + } + this.showTrashLibraries(); + }, + + showTrashLibraries: function() { + this.initPage(); + var _this = this; + + this.trashRepoCollection.fetch({ + data: {'owner': this.option.owner}, + cache: false, + reset: true, + error: function (collection, response, opts) { + var err_msg; + if (response.responseText) { + if (response['status'] == 401 || response['status'] == 403) { + err_msg = gettext("Permission error"); + } else { + err_msg = $.parseJSON(response.responseText).error_msg; + } + } else { + err_msg = gettext("Failed. Please check the network."); + } + Common.feedback(err_msg, 'error'); + }, + complete: function() { + _this.$loadingTip.hide(); + } + }); + }, + + reset: function() { + var length = this.trashRepoCollection.length; + + this.$loadingTip.hide(); + + if (length > 0) { + this.trashRepoCollection.each(this.addOne, this); + this.$cleanBtn.show(); + this.$tip.show(); + this.$table.show(); + } else { + this.$emptyTip.show(); + this.$cleanBtn.hide(); + } + }, + + addOne: function(library) { + var view = new TrashRepoView({model: library}); + this.$tableBody.append(view.render().el); + } + }); + + return TrashReposView; + +}); diff --git a/static/scripts/sysadmin-app/views/trash-repos.js b/static/scripts/sysadmin-app/views/trash-repos.js index fa11548c3c..b7e912dd70 100644 --- a/static/scripts/sysadmin-app/views/trash-repos.js +++ b/static/scripts/sysadmin-app/views/trash-repos.js @@ -34,33 +34,32 @@ define([ this.$loadingTip = this.$('.loading-tip'); this.$emptyTip = this.$('.empty-tips'); this.$cleanBtn = this.$('.js-clean'); + this.$jsPrevious = this.$('.js-previous'); + this.$jsNext = this.$('.js-next'); }, events: { - 'click .js-clean': 'cleanTrashLibraries' + 'click .js-clean': 'cleanTrashLibraries', + 'click #paginator .js-next': 'getNextPage', + 'click #paginator .js-previous': 'getPreviousPage' }, cleanTrashLibraries: function() { var _this = this; - var popupTitle, popupContent; - var owner = this.trashRepoCollection.search_owner; - if (owner) { - popupTitle = gettext("Delete Library By Owner"); - popupContent = gettext("Are you sure you want to delete all %s's libraries?").replace('%s', '' + Common.HTMLescape(owner) + ''); - } else { - popupTitle = gettext("Clear Trash"); - popupContent = gettext("Are you sure you want to clear trash?"); - } + var popupTitle = gettext("Clear Trash"); + var popupContent = gettext("Are you sure you want to clear trash?"); var yesCallback = function() { $.ajax({ url: Common.getUrl({'name':'admin-trash-libraries'}), type: 'DELETE', - data: {'owner': _this.option.owner}, beforeSend: Common.prepareCSRFToken, dataType: 'json', success: function() { + _this.$cleanBtn.hide(); _this.$tip.hide(); _this.$table.hide(); + _this.$jsNext.hide(); + _this.$jsPrevious.hide(); _this.$emptyTip.show(); Common.feedback(gettext("Success"), 'success'); }, @@ -75,6 +74,29 @@ define([ Common.showConfirm(popupTitle, popupContent, yesCallback); }, + getNextPage: function() { + this.initPage(); + var current_page = this.trashRepoCollection.state.current_page; + if (this.trashRepoCollection.state.has_next_page) { + this.trashRepoCollection.getPage(current_page + 1, { + reset: true + }); + } + + return false; + }, + + getPreviousPage: function() { + this.initPage(); + var current_page = this.trashRepoCollection.state.current_page; + if (current_page > 1) { + this.trashRepoCollection.getPage(current_page - 1, { + reset: true + }); + } + return false; + }, + initPage: function() { this.$tip.hide(); this.$table.hide(); @@ -82,6 +104,8 @@ define([ this.$loadingTip.show(); this.$emptyTip.hide(); this.$cleanBtn.hide(); + this.$jsNext.hide(); + this.$jsPrevious.hide(); }, hide: function() { @@ -103,7 +127,7 @@ define([ var _this = this; this.trashRepoCollection.fetch({ - data: {'owner': this.option.owner}, + data: {'page': this.option.page}, cache: false, reset: true, error: function (collection, response, opts) { @@ -123,21 +147,38 @@ define([ }, reset: function() { - var length = this.trashRepoCollection.length; + // update the url + var current_page = this.trashRepoCollection.state.current_page; + app.router.navigate('trash-libs/?page=' + current_page); this.$loadingTip.hide(); - - if (length > 0) { + if (this.trashRepoCollection.length > 0) { this.trashRepoCollection.each(this.addOne, this); this.$cleanBtn.show(); this.$tip.show(); this.$table.show(); + this.renderPaginator(); } else { this.$emptyTip.show(); this.$cleanBtn.hide(); } }, + renderPaginator: function() { + if (this.trashRepoCollection.state.has_next_page) { + this.$jsNext.show(); + } else { + this.$jsNext.hide(); + } + + var current_page = this.trashRepoCollection.state.current_page; + if (current_page > 1) { + this.$jsPrevious.show(); + } else { + this.$jsPrevious.hide(); + } + }, + addOne: function(library) { var view = new TrashRepoView({model: library}); this.$tableBody.append(view.render().el);