diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index b865a5209d..02e9f9acda 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -760,6 +760,53 @@ class LibContentView extends React.Component { }); } + restoreDeletedDirents = (commitID, paths, e) => { + const { repoID } = this.props; + e.preventDefault(); + toaster.closeAll(); + seafileAPI.restoreDirents(repoID, commitID, paths).then(res => { + const { success, failed } = res.data; + success.forEach(dirent => { + let name = Utils.getFileName(dirent.path); + let parentPath = Utils.getDirName(dirent.path); + if (!dirent.is_dir) { + if (this.state.currentMode === 'column') { + this.addNodeToTree(name, parentPath, 'file'); + } + if (parentPath === this.state.path && !this.state.isViewFile) { + this.addDirent(name, 'file'); + } + } else { + if (this.state.currentMode === 'column') { + this.addNodeToTree(name, parentPath, 'dir'); + } + if (parentPath === this.state.path && !this.state.isViewFile) { + this.addDirent(name, 'dir'); + } + } + }); + + if (success.length) { + let msg = success.length > 1 ? gettext('Restored {name} and {n} other items') : + gettext('Restored {name}'); + msg = msg.replace('{name}', success[0].path.split('/').pop()) + .replace('{n}', success.length - 1); + toaster.success(msg); + } + + if (failed.length) { + let msg = failed.length > 1 ? gettext('Failed to restore {name} and {n} other items') : + gettext('Failed to restore {name}'); + msg = msg.replace('{name}', failed[0].path.split('/').pop()) + .replace('{n}', failed.length - 1); + toaster.danger(msg); + } + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + onDeleteItems = () => { let repoID = this.props.repoID; let direntPaths = this.getSelectedDirentPaths(); @@ -775,18 +822,24 @@ class LibContentView extends React.Component { let msg = ''; if (direntPaths.length > 1) { - msg = gettext('Successfully deleted {name} and other {n} items.'); + msg = gettext('Successfully deleted {name} and {n} other items.'); msg = msg.replace('{name}', dirNames[0]); msg = msg.replace('{n}', dirNames.length - 1); } else { msg = gettext('Successfully deleted {name}.'); msg = msg.replace('{name}', dirNames[0]); } - toaster.success(msg); + const successTipWithUndo = ( + <> + {msg} + {gettext('Undo')} + + ); + toaster.success(successTipWithUndo, {duration: 5}); }).catch((error) => { let errMessage = Utils.getErrorMsg(error); if (errMessage === gettext('Error')) { - errMessage = gettext('Failed to delete {name} and other {n} items.'); + errMessage = gettext('Failed to delete {name} and {n} other items.'); errMessage = errMessage.replace('{name}', dirNames[0]); errMessage = errMessage.replace('{n}', dirNames.length - 1); } diff --git a/seahub/api2/endpoints/repo_trash.py b/seahub/api2/endpoints/repo_trash.py index 232a1ca8f3..b65cd98cc8 100644 --- a/seahub/api2/endpoints/repo_trash.py +++ b/seahub/api2/endpoints/repo_trash.py @@ -251,3 +251,61 @@ class RepoTrash(APIView): return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) return Response({'success': True}) + + +class RepoTrashRevertDirents(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated, ) + throttle_classes = (UserRateThrottle, ) + + def post(self, request, repo_id): + """ Revert deleted files/dirs. + """ + + # argument check + path_list = request.data.getlist('path', []) + if not path_list: + error_msg = 'path invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + commit_id = request.data.get('commit_id', '') + if not commit_id: + error_msg = 'commit_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # permission check + if check_folder_permission(request, repo_id, '/') != 'rw': + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + username = request.user.username + for path in path_list: + try: + if seafile_api.get_dir_id_by_commit_and_path(repo_id, commit_id, path): + seafile_api.revert_dir(repo_id, commit_id, path, username) + result['success'].append({'path': path, 'is_dir': True}) + elif seafile_api.get_file_id_by_commit_and_path(repo_id, commit_id, path): + seafile_api.revert_file(repo_id, commit_id, path, username) + result['success'].append({'path': path, 'is_dir': False}) + else: + result['failed'].append({ + 'path': path, + 'error_msg': f'Dirent {path} not found.' + }) + except Exception as e: + result['failed'].append({ + 'path': path, + 'error_msg': str(e) + }) + + return Response(result) diff --git a/seahub/api2/endpoints/repos_batch.py b/seahub/api2/endpoints/repos_batch.py index 989ce68d74..e2e7602756 100644 --- a/seahub/api2/endpoints/repos_batch.py +++ b/seahub/api2/endpoints/repos_batch.py @@ -1535,7 +1535,8 @@ class ReposBatchDeleteItemView(APIView): return api_error(status.HTTP_400_BAD_REQUEST, error_msg) # resource check - if not seafile_api.get_repo(repo_id): + repo = seafile_api.get_repo(repo_id) + if not repo: error_msg = 'Library %s not found.' % repo_id return api_error(status.HTTP_404_NOT_FOUND, error_msg) @@ -1580,4 +1581,5 @@ class ReposBatchDeleteItemView(APIView): result = {} result['success'] = True + result['commit_id'] = repo.head_cmmt_id return Response(result) diff --git a/seahub/urls.py b/seahub/urls.py index c5e4bee605..23791a1d7a 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -61,7 +61,7 @@ from seahub.api2.endpoints.file_history import FileHistoryView, NewFileHistoryVi from seahub.api2.endpoints.dir import DirView, DirDetailView from seahub.api2.endpoints.file_tag import FileTagView from seahub.api2.endpoints.file_tag import FileTagsView -from seahub.api2.endpoints.repo_trash import RepoTrash +from seahub.api2.endpoints.repo_trash import RepoTrash, RepoTrashRevertDirents from seahub.api2.endpoints.repo_commit import RepoCommitView from seahub.api2.endpoints.repo_commit_dir import RepoCommitDirView from seahub.api2.endpoints.repo_commit_revert import RepoCommitRevertView @@ -401,6 +401,7 @@ urlpatterns = [ url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/commits/(?P[0-9a-f]{40})/revert/$', RepoCommitRevertView.as_view(), name='api-v2.1-repo-commit-revert'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/dir/detail/$', DirDetailView.as_view(), name='api-v2.1-dir-detail-view'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/trash/$', RepoTrash.as_view(), name='api-v2.1-repo-trash'), + url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/trash/revert-dirents/$', RepoTrashRevertDirents.as_view(), name='api-v2.1-repo-trash-revert-dirents'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/history/$', RepoHistory.as_view(), name='api-v2.1-repo-history'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/set-password/$', RepoSetPassword.as_view(), name="api-v2.1-repo-set-password"), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/send-new-password/$', RepoSendNewPassword.as_view(), name="api-v2.1-repo-send-new-password"),