diff --git a/frontend/src/components/dialog/generate-share-link.js b/frontend/src/components/dialog/generate-share-link.js index 28d96fc88d..88a0ad3364 100644 --- a/frontend/src/components/dialog/generate-share-link.js +++ b/frontend/src/components/dialog/generate-share-link.js @@ -51,6 +51,8 @@ class GenerateShareLink extends React.Component { isShowPasswordInput: shareLinkForceUsePassword ? true : false, isPasswordVisible: false, isExpireChecked: !this.isExpireDaysNoLimit, + isExpirationEditIconShow: false, + isEditingExpiration: false, setExp: 'by-days', expireDays: this.defaultExpireDays, expDate: null, @@ -337,6 +339,46 @@ class GenerateShareLink extends React.Component { return true; } + handleMouseOverExpirationEditIcon = () => { + this.setState({isExpirationEditIconShow: true}); + } + + handleMouseOutExpirationEditIcon = () => { + this.setState({isExpirationEditIconShow: false}); + } + + editingExpirationToggle = () => { + this.setState({isEditingExpiration: !this.state.isEditingExpiration}); + } + + updateExpiration = (e) => { + + e.preventDefault(); + e.nativeEvent.stopImmediatePropagation(); + + let { setExp, expireDays, expDate } = this.state; + + let expirationTime = ''; + if (setExp == 'by-days') { + expirationTime = moment().add(parseInt(expireDays), 'days').format(); + } else { + expirationTime = expDate.format(); + } + + seafileAPI.updateShareLink(this.state.sharedLinkInfo.token, '', expirationTime).then((res) => { + let sharedLinkInfo = new ShareLink(res.data); + this.setState({ + sharedLinkInfo: sharedLinkInfo, + isEditingExpiration: false, + }); + let message = gettext('Successfully update expiration.'); + toaster.success(message); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + onNoticeMessageToggle = () => { this.setState({isNoticeMessageShow: !this.state.isNoticeMessageShow}); } @@ -421,7 +463,59 @@ class GenerateShareLink extends React.Component { {sharedLinkInfo.expire_date && (
{gettext('Expiration Date:')}
-
{moment(sharedLinkInfo.expire_date).format('YYYY-MM-DD HH:mm:ss')}
+ {!this.state.isEditingExpiration && +
+ {moment(sharedLinkInfo.expire_date).format('YYYY-MM-DD HH:mm:ss')} + {this.state.isExpirationEditIconShow && ( + + + )} +
+ } + {this.state.isEditingExpiration && +
+ + + {this.state.setExp == 'by-days' && ( + + + + + {gettext('days')} + + + {!this.state.isExpireDaysNoLimit && ( + {this.expirationLimitTip} + )} + + )} + + + + {this.state.setExp == 'by-date' && ( + + )} + + {' '} + +
+ }
)} diff --git a/frontend/src/components/dialog/generate-upload-link.js b/frontend/src/components/dialog/generate-upload-link.js index ce44d629db..463db4f643 100644 --- a/frontend/src/components/dialog/generate-upload-link.js +++ b/frontend/src/components/dialog/generate-upload-link.js @@ -50,6 +50,8 @@ class GenerateUploadLink extends React.Component { sharedUploadInfo: null, isSendLinkShown: false, isExpireChecked: !this.isExpireDaysNoLimit, + isExpirationEditIconShow: false, + isEditingExpiration: false, setExp: 'by-days', expireDays: this.defaultExpireDays, expDate: null @@ -195,11 +197,11 @@ class GenerateUploadLink extends React.Component { this.setState({isExpireChecked: e.target.checked}); } - setExp = (e) => { - this.setState({ - setExp: e.target.value - }); - } + setExp = (e) => { + this.setState({ + setExp: e.target.value + }); + } disabledDate = (current) => { if (!current) { @@ -240,6 +242,46 @@ class GenerateUploadLink extends React.Component { this.props.closeShareDialog(); } + handleMouseOverExpirationEditIcon = () => { + this.setState({isExpirationEditIconShow: true}); + } + + handleMouseOutExpirationEditIcon = () => { + this.setState({isExpirationEditIconShow: false}); + } + + editExpirationToggle = () => { + this.setState({isEditingExpiration: !this.state.isEditingExpiration}); + } + + updateExpiration = (e) => { + + e.preventDefault(); + e.nativeEvent.stopImmediatePropagation(); + + let { setExp, expireDays, expDate } = this.state; + + let expirationTime = ''; + if (setExp == 'by-days') { + expirationTime = moment().add(parseInt(expireDays), 'days').format(); + } else { + expirationTime = expDate.format(); + } + + seafileAPI.updateUploadLink(this.state.sharedUploadInfo.token, expirationTime).then((res) => { + let sharedUploadInfo = new UploadLink(res.data); + this.setState({ + sharedUploadInfo: sharedUploadInfo, + isEditingExpiration: false, + }); + let message = gettext('Successfully update expiration.'); + toaster.success(message); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + deleteUploadLink = () => { let sharedUploadInfo = this.state.sharedUploadInfo; seafileAPI.deleteUploadLink(sharedUploadInfo.token).then(() => { @@ -302,7 +344,59 @@ class GenerateUploadLink extends React.Component { {sharedUploadInfo.expire_date && (
{gettext('Expiration Date:')}
-
{moment(sharedUploadInfo.expire_date).format('YYYY-MM-DD HH:mm:ss')}
+ {!this.state.isEditingExpiration && +
+ {moment(sharedUploadInfo.expire_date).format('YYYY-MM-DD HH:mm:ss')} + {this.state.isExpirationEditIconShow && ( + + + )} +
+ } + {this.state.isEditingExpiration && +
+ + + {this.state.setExp == 'by-days' && ( + + + + + {gettext('days')} + + + {!this.state.isExpireDaysNoLimit && ( + {this.expirationLimitTip} + )} + + )} + + + + {this.state.setExp == 'by-date' && ( + + )} + + {' '} + +
+ }
)} diff --git a/seahub/api2/endpoints/share_links.py b/seahub/api2/endpoints/share_links.py index 7e84ce9d52..7178d4a281 100644 --- a/seahub/api2/endpoints/share_links.py +++ b/seahub/api2/endpoints/share_links.py @@ -447,19 +447,12 @@ class ShareLink(APIView): return Response(link_info) def put(self, request, token): - """ Update share link, currently only available for permission. + """ Update share link's permission and expiration. Permission checking: share link creater """ - # argument check - try: - perm = check_permissions_arg(request) - except Exception: - error_msg = 'permissions invalud.' - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - # resource check try: fs = FileShare.objects.get(token=token) @@ -500,26 +493,102 @@ class ShareLink(APIView): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - if repo_folder_permission in (PERMISSION_PREVIEW_EDIT, PERMISSION_PREVIEW) \ - and perm != FileShare.PERM_VIEW_ONLY: - error_msg = 'Permission denied.' - return api_error(status.HTTP_403_FORBIDDEN, error_msg) + # argument check + permissions = request.data.get('permissions', '') + if permissions: + try: + perm = check_permissions_arg(request) + except Exception: + error_msg = 'permissions invalud.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - if repo_folder_permission in (PERMISSION_READ) \ - and perm not in (FileShare.PERM_VIEW_DL, FileShare.PERM_VIEW_ONLY): - error_msg = 'Permission denied.' - return api_error(status.HTTP_403_FORBIDDEN, error_msg) - - if fs.s_type == 'f': - file_name = os.path.basename(fs.path.rstrip('/')) - can_edit, error_msg = can_edit_file(file_name, dirent.size, repo) - if not can_edit and perm in (FileShare.PERM_EDIT_DL, FileShare.PERM_EDIT_ONLY): + if repo_folder_permission in (PERMISSION_PREVIEW_EDIT, PERMISSION_PREVIEW) \ + and perm != FileShare.PERM_VIEW_ONLY: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - # update share link permission - fs.permission = perm - fs.save() + if repo_folder_permission in (PERMISSION_READ) \ + and perm not in (FileShare.PERM_VIEW_DL, FileShare.PERM_VIEW_ONLY): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + if fs.s_type == 'f': + file_name = os.path.basename(fs.path.rstrip('/')) + can_edit, error_msg = can_edit_file(file_name, dirent.size, repo) + if not can_edit and perm in (FileShare.PERM_EDIT_DL, FileShare.PERM_EDIT_ONLY): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + # update share link permission + fs.permission = perm + fs.save() + + expire_days = request.data.get('expire_days', '') + expiration_time = request.data.get('expiration_time', '') + + if expire_days and expiration_time: + error_msg = 'Can not pass expire_days and expiration_time at the same time.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if expire_days: + + try: + expire_days = int(expire_days) + except ValueError: + error_msg = 'expire_days invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if expire_days <= 0: + error_msg = 'expire_days invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if SHARE_LINK_EXPIRE_DAYS_MIN > 0: + if expire_days < SHARE_LINK_EXPIRE_DAYS_MIN: + error_msg = _('Expire days should be greater or equal to %s') % \ + SHARE_LINK_EXPIRE_DAYS_MIN + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if SHARE_LINK_EXPIRE_DAYS_MAX > 0: + if expire_days > SHARE_LINK_EXPIRE_DAYS_MAX: + error_msg = _('Expire days should be less than or equal to %s') % \ + SHARE_LINK_EXPIRE_DAYS_MAX + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + expire_date = timezone.now() + relativedelta(days=expire_days) + fs.expire_date = expire_date + fs.save() + + if expiration_time: + + try: + expire_date = dateutil.parser.isoparse(expiration_time) + except Exception as e: + logger.error(e) + error_msg = 'expiration_time invalid, should be iso format, for example: 2020-05-17T10:26:22+08:00' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + expire_date = expire_date.astimezone(get_current_timezone()).replace(tzinfo=None) + + if SHARE_LINK_EXPIRE_DAYS_MIN > 0: + expire_date_min_limit = timezone.now() + relativedelta(days=SHARE_LINK_EXPIRE_DAYS_MIN) + expire_date_min_limit = expire_date_min_limit.replace(hour=0).replace(minute=0).replace(second=0) + + if expire_date < expire_date_min_limit: + error_msg = _('Expiration time should be later than %s.') % \ + expire_date_min_limit.strftime("%Y-%m-%d %H:%M:%S") + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if SHARE_LINK_EXPIRE_DAYS_MAX > 0: + expire_date_max_limit = timezone.now() + relativedelta(days=SHARE_LINK_EXPIRE_DAYS_MAX) + expire_date_max_limit = expire_date_max_limit.replace(hour=23).replace(minute=59).replace(second=59) + + if expire_date > expire_date_max_limit: + error_msg = _('Expiration time should be earlier than %s.') % \ + expire_date_max_limit.strftime("%Y-%m-%d %H:%M:%S") + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + fs.expire_date = expire_date + fs.save() link_info = get_share_link_info(fs) return Response(link_info) diff --git a/seahub/api2/endpoints/upload_links.py b/seahub/api2/endpoints/upload_links.py index 2d616350f0..935d2e4c9e 100644 --- a/seahub/api2/endpoints/upload_links.py +++ b/seahub/api2/endpoints/upload_links.py @@ -294,6 +294,88 @@ class UploadLink(APIView): link_info = get_upload_link_info(uls) return Response(link_info) + def put(self, request, token): + """ Update upload link's expiration. + + Permission checking: + upload link creater + """ + + try: + uls = UploadLinkShare.objects.get(token=token) + except UploadLinkShare.DoesNotExist: + error_msg = 'token %s not found.' % token + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + expire_days = request.data.get('expire_days', '') + expiration_time = request.data.get('expiration_time', '') + if expire_days and expiration_time: + error_msg = 'Can not pass expire_days and expiration_time at the same time.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + expire_date = None + if expire_days: + try: + expire_days = int(expire_days) + except ValueError: + error_msg = 'expire_days invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if expire_days <= 0: + error_msg = 'expire_days invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if UPLOAD_LINK_EXPIRE_DAYS_MIN > 0: + if expire_days < UPLOAD_LINK_EXPIRE_DAYS_MIN: + error_msg = _('Expire days should be greater or equal to %s') % \ + UPLOAD_LINK_EXPIRE_DAYS_MIN + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if UPLOAD_LINK_EXPIRE_DAYS_MAX > 0: + if expire_days > UPLOAD_LINK_EXPIRE_DAYS_MAX: + error_msg = _('Expire days should be less than or equal to %s') % \ + UPLOAD_LINK_EXPIRE_DAYS_MAX + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + expire_date = timezone.now() + relativedelta(days=expire_days) + uls.expire_date = expire_date + uls.save() + + elif expiration_time: + + try: + expire_date = dateutil.parser.isoparse(expiration_time) + except Exception as e: + logger.error(e) + error_msg = 'expiration_time invalid, should be iso format, for example: 2020-05-17T10:26:22+08:00' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + expire_date = expire_date.astimezone(get_current_timezone()).replace(tzinfo=None) + + if UPLOAD_LINK_EXPIRE_DAYS_MIN > 0: + expire_date_min_limit = timezone.now() + relativedelta(days=UPLOAD_LINK_EXPIRE_DAYS_MIN) + expire_date_min_limit = expire_date_min_limit.replace(hour=0).replace(minute=0).replace(second=0) + + if expire_date < expire_date_min_limit: + error_msg = _('Expiration time should be later than %s.') % \ + expire_date_min_limit.strftime("%Y-%m-%d %H:%M:%S") + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if UPLOAD_LINK_EXPIRE_DAYS_MAX > 0: + expire_date_max_limit = timezone.now() + relativedelta(days=UPLOAD_LINK_EXPIRE_DAYS_MAX) + expire_date_max_limit = expire_date_max_limit.replace(hour=23).replace(minute=59).replace(second=59) + + if expire_date > expire_date_max_limit: + error_msg = _('Expiration time should be earlier than %s.') % \ + expire_date_max_limit.strftime("%Y-%m-%d %H:%M:%S") + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + uls.expire_date = expire_date + uls.save() + + link_info = get_upload_link_info(uls) + return Response(link_info) + def delete(self, request, token): """ Delete upload link.