@@ -511,39 +549,17 @@ class GenerateShareLink extends React.Component {
{this.state.isExpireChecked &&
-
-
- {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..f6ec12affc 100644
--- a/frontend/src/components/dialog/generate-upload-link.js
+++ b/frontend/src/components/dialog/generate-upload-link.js
@@ -1,16 +1,16 @@
-import React, { Fragment } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import copy from 'copy-to-clipboard';
import moment from 'moment';
-import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, InputGroupText, Alert, FormText } from 'reactstrap';
+import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, Alert } from 'reactstrap';
import { gettext, shareLinkForceUsePassword, shareLinkPasswordMinLength, shareLinkPasswordStrengthLevel, canSendShareLinkEmail, uploadLinkExpireDaysMin, uploadLinkExpireDaysMax, uploadLinkExpireDaysDefault } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import UploadLink from '../../models/upload-link';
import toaster from '../toast';
import SendLink from '../send-link';
-import DateTimePicker from '../date-and-time-picker';
import SharedLink from '../shared-link';
+import SetLinkExpiration from '../set-link-expiration';
const propTypes = {
itemPath: PropTypes.string.isRequired,
@@ -27,20 +27,6 @@ class GenerateUploadLink extends React.Component {
this.isExpireDaysNoLimit = (uploadLinkExpireDaysMin === 0 && uploadLinkExpireDaysMax === 0 && uploadLinkExpireDaysDefault == 0);
this.defaultExpireDays = this.isExpireDaysNoLimit ? '' : uploadLinkExpireDaysDefault;
- let expirationLimitTip = '';
- if (uploadLinkExpireDaysMin !== 0 && uploadLinkExpireDaysMax !== 0) {
- expirationLimitTip = gettext('{minDays_placeholder} - {maxDays_placeholder} days')
- .replace('{minDays_placeholder}', uploadLinkExpireDaysMin)
- .replace('{maxDays_placeholder}', uploadLinkExpireDaysMax);
- } else if (uploadLinkExpireDaysMin !== 0 && uploadLinkExpireDaysMax === 0) {
- expirationLimitTip = gettext('Greater than or equal to {minDays_placeholder} days')
- .replace('{minDays_placeholder}', uploadLinkExpireDaysMin);
- } else if (uploadLinkExpireDaysMin === 0 && uploadLinkExpireDaysMax !== 0) {
- expirationLimitTip = gettext('Less than or equal to {maxDays_placeholder} days')
- .replace('{maxDays_placeholder}', uploadLinkExpireDaysMax);
- }
- this.expirationLimitTip = expirationLimitTip;
-
this.state = {
showPasswordInput: shareLinkForceUsePassword ? true : false,
passwordVisible: false,
@@ -50,7 +36,9 @@ class GenerateUploadLink extends React.Component {
sharedUploadInfo: null,
isSendLinkShown: false,
isExpireChecked: !this.isExpireDaysNoLimit,
- setExp: 'by-days',
+ isExpirationEditIconShow: false,
+ isEditingExpiration: false,
+ expType: 'by-days',
expireDays: this.defaultExpireDays,
expDate: null
};
@@ -124,11 +112,11 @@ class GenerateUploadLink extends React.Component {
this.setState({errorInfo: ''});
let { itemPath, repoID } = this.props;
- let { password, isExpireChecked, setExp, expireDays, expDate } = this.state;
+ let { password, isExpireChecked, expType, expireDays, expDate } = this.state;
let expirationTime = '';
if (isExpireChecked) {
- if (setExp == 'by-days') {
+ if (expType == 'by-days') {
expirationTime = moment().add(parseInt(expireDays), 'days').format();
} else {
expirationTime = expDate.format();
@@ -146,7 +134,7 @@ class GenerateUploadLink extends React.Component {
}
validateParamsInput = () => {
- let { showPasswordInput, password, passwordnew, isExpireChecked, setExp, expireDays, expDate } = this.state;
+ let { showPasswordInput, password, passwordnew, isExpireChecked, expType, expireDays, expDate } = this.state;
// check password params
if (showPasswordInput) {
@@ -169,7 +157,7 @@ class GenerateUploadLink extends React.Component {
}
if (isExpireChecked) {
- if (setExp == 'by-date') {
+ if (expType == 'by-date') {
if (!expDate) {
this.setState({errorInfo: gettext('Please select an expiration time')});
return false;
@@ -195,31 +183,10 @@ class GenerateUploadLink extends React.Component {
this.setState({isExpireChecked: e.target.checked});
}
- setExp = (e) => {
- this.setState({
- setExp: e.target.value
- });
- }
-
- disabledDate = (current) => {
- if (!current) {
- // allow empty select
- return false;
- }
-
- if (this.isExpireDaysNoLimit) {
- return current.isBefore(moment(), 'day');
- }
-
- const startDay = moment().add(uploadLinkExpireDaysMin, 'days');
- const endDay = moment().add(uploadLinkExpireDaysMax, 'days');
- if (uploadLinkExpireDaysMin !== 0 && uploadLinkExpireDaysMax !== 0) {
- return current.isBefore(startDay, 'day') || current.isAfter(endDay, 'day');
- } else if (uploadLinkExpireDaysMin !== 0 && uploadLinkExpireDaysMax === 0) {
- return current.isBefore(startDay, 'day');
- } else if (uploadLinkExpireDaysMin === 0 && uploadLinkExpireDaysMax !== 0) {
- return current.isBefore(moment(), 'day') || current.isAfter(endDay, 'day');
- }
+ setExpType = (e) => {
+ this.setState({
+ expType: e.target.value
+ });
}
onExpDateChanged = (value) => {
@@ -240,12 +207,51 @@ 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 { expType, expireDays, expDate } = this.state;
+
+ let expirationTime = '';
+ if (expType == '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,
+ });
+ }).catch((error) => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+
deleteUploadLink = () => {
let sharedUploadInfo = this.state.sharedUploadInfo;
seafileAPI.deleteUploadLink(sharedUploadInfo.token).then(() => {
this.setState({
showPasswordInput: shareLinkForceUsePassword ? true : false,
expireDays: this.defaultExpireDays,
+ expDate: null,
isExpireChecked: !this.isExpireDaysNoLimit,
password: '',
passwordnew: '',
@@ -269,7 +275,7 @@ class GenerateUploadLink extends React.Component {
let passwordLengthTip = gettext('(at least {passwordMinLength} characters and includes {passwordStrengthLevel} of the following: number, upper letter, lower letter and other symbols)');
passwordLengthTip = passwordLengthTip.replace('{passwordMinLength}', shareLinkPasswordMinLength)
- .replace('{passwordStrengthLevel}', shareLinkPasswordStrengthLevel);
+ .replace('{passwordStrengthLevel}', shareLinkPasswordStrengthLevel);
if (this.state.sharedUploadInfo) {
let sharedUploadInfo = this.state.sharedUploadInfo;
@@ -302,7 +308,39 @@ 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 &&
+
+
+
+
+
+
+
+ }
)}
@@ -324,15 +362,15 @@ class GenerateUploadLink extends React.Component {
{this.state.showPasswordInput &&
@@ -366,39 +404,17 @@ class GenerateUploadLink extends React.Component {
{this.state.isExpireChecked &&
-
-
-
- {gettext('Expiration days')}
-
- {this.state.setExp == 'by-days' && (
-
-
-
-
- {gettext('days')}
-
-
- {!this.state.isExpireDaysNoLimit && (
- {this.expirationLimitTip}
- )}
-
- )}
-
-
-
-
- {gettext('Expiration time')}
-
- {this.state.setExp == 'by-date' && (
-
- )}
-
+
}
diff --git a/frontend/src/components/set-link-expiration.js b/frontend/src/components/set-link-expiration.js
new file mode 100644
index 0000000000..08d8d9ec33
--- /dev/null
+++ b/frontend/src/components/set-link-expiration.js
@@ -0,0 +1,119 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import { FormGroup, Label, Input, InputGroup, InputGroupAddon, InputGroupText, FormText } from 'reactstrap';
+import { gettext } from '../utils/constants';
+import { Utils } from '../utils/utils';
+import DateTimePicker from './date-and-time-picker';
+
+const propTypes = {
+ minDays: PropTypes.number.isRequired,
+ maxDays: PropTypes.number.isRequired,
+ defaultDays: PropTypes.number.isRequired,
+ expType: PropTypes.string.isRequired,
+ setExpType: PropTypes.func.isRequired,
+ expireDays: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number
+ ]),
+ onExpireDaysChanged: PropTypes.func.isRequired,
+ expDate: PropTypes.object,
+ onExpDateChanged: PropTypes.func.isRequired
+};
+
+const inputWidth = Utils.isDesktop() ? 250 : 210;
+
+class SetLinkExpiration extends React.Component {
+
+ constructor(props) {
+ super(props);
+
+ const { minDays, maxDays, defaultDays } = this.props;
+ this.isExpireDaysNoLimit = (minDays === 0 && maxDays === 0 && defaultDays == 0);
+
+ let expirationLimitTip = '';
+ if (minDays !== 0 && maxDays !== 0) {
+ expirationLimitTip = gettext('{minDays_placeholder} - {maxDays_placeholder} days')
+ .replace('{minDays_placeholder}', minDays)
+ .replace('{maxDays_placeholder}', maxDays);
+ } else if (minDays !== 0 && maxDays === 0) {
+ expirationLimitTip = gettext('Greater than or equal to {minDays_placeholder} days')
+ .replace('{minDays_placeholder}', minDays);
+ } else if (minDays === 0 && maxDays !== 0) {
+ expirationLimitTip = gettext('Less than or equal to {maxDays_placeholder} days')
+ .replace('{maxDays_placeholder}', maxDays);
+ }
+ this.expirationLimitTip = expirationLimitTip;
+ }
+
+ disabledDate = (current) => {
+ if (!current) {
+ // allow empty select
+ return false;
+ }
+
+ if (this.isExpireDaysNoLimit) {
+ return current.isBefore(moment(), 'day');
+ }
+
+ const { minDays, maxDays } = this.props;
+ const startDay = moment().add(minDays, 'days');
+ const endDay = moment().add(maxDays, 'days');
+ if (minDays !== 0 && maxDays !== 0) {
+ return current.isBefore(startDay, 'day') || current.isAfter(endDay, 'day');
+ } else if (minDays !== 0 && maxDays === 0) {
+ return current.isBefore(startDay, 'day');
+ } else if (minDays === 0 && maxDays !== 0) {
+ return current.isBefore(moment(), 'day') || current.isAfter(endDay, 'day');
+ }
+ }
+
+ render() {
+ const {
+ expType, setExpType,
+ expireDays, onExpireDaysChanged,
+ expDate, onExpDateChanged
+ } = this.props;
+ return (
+
+
+
+
+ {gettext('Expiration days')}
+
+ {expType == 'by-days' && (
+
+
+
+
+ {gettext('days')}
+
+
+ {!this.isExpireDaysNoLimit && (
+ {this.expirationLimitTip}
+ )}
+
+ )}
+
+
+
+
+ {gettext('Expiration time')}
+
+ {expType == 'by-date' && (
+
+ )}
+
+
+ );
+ }
+}
+
+SetLinkExpiration.propTypes = propTypes;
+
+export default SetLinkExpiration;
diff --git a/seahub/api2/endpoints/share_links.py b/seahub/api2/endpoints/share_links.py
index 7e84ce9d52..4dcde26a95 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 invalid.'
+ 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.