diff --git a/frontend/src/components/dialog/share-dialog.js b/frontend/src/components/dialog/share-dialog.js
index 269098486e..f2e539482a 100644
--- a/frontend/src/components/dialog/share-dialog.js
+++ b/frontend/src/components/dialog/share-dialog.js
@@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
import { gettext, username, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, additionalShareDialogNote, enableOCM, isPro } from '../../utils/constants';
-import ShareLinkPanel from './share-link-panel';
+import ShareLinkPanel from '../share-link-panel';
import GenerateUploadLink from './generate-upload-link';
import ShareToUser from './share-to-user';
import ShareToGroup from './share-to-group';
diff --git a/frontend/src/components/dialog/share-link-panel.js b/frontend/src/components/dialog/share-link-panel.js
deleted file mode 100644
index 6fe7f3055c..0000000000
--- a/frontend/src/components/dialog/share-link-panel.js
+++ /dev/null
@@ -1,748 +0,0 @@
-import React, { Fragment } from 'react';
-import PropTypes from 'prop-types';
-import moment from 'moment';
-import copy from 'copy-to-clipboard';
-import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, Alert } from 'reactstrap';
-import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, shareLinkForceUsePassword, shareLinkPasswordMinLength, shareLinkPasswordStrengthLevel, canSendShareLinkEmail } from '../../utils/constants';
-import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor';
-import { seafileAPI } from '../../utils/seafile-api';
-import { Utils } from '../../utils/utils';
-import ShareLink from '../../models/share-link';
-import toaster from '../toast';
-import Loading from '../loading';
-import SendLink from '../send-link';
-import SharedLink from '../shared-link';
-import SetLinkExpiration from '../set-link-expiration';
-
-const propTypes = {
- itemPath: PropTypes.string.isRequired,
- repoID: PropTypes.string.isRequired,
- closeShareDialog: PropTypes.func.isRequired,
- userPerm: PropTypes.string,
- itemType: PropTypes.string
-};
-
-const inputWidth = Utils.isDesktop() ? 250 : 210;
-
-class ShareLinkPanel extends React.Component {
-
- constructor(props) {
- super(props);
-
- this.isExpireDaysNoLimit = (shareLinkExpireDaysMin === 0 && shareLinkExpireDaysMax === 0 && shareLinkExpireDaysDefault == 0);
- this.defaultExpireDays = this.isExpireDaysNoLimit ? '' : shareLinkExpireDaysDefault;
-
- this.state = {
- isShowPasswordInput: shareLinkForceUsePassword ? true : false,
- isPasswordVisible: false,
- isExpireChecked: !this.isExpireDaysNoLimit,
- expType: 'by-days',
- expireDays: this.defaultExpireDays,
- expDate: null,
- password: '',
- passwdnew: '',
- errorInfo: '',
- sharedLinkInfo: null,
- shareLinks: [],
- isLoading: true,
- permissionOptions: [],
- currentPermission: '',
- };
- }
-
- componentDidMount() {
- let path = this.props.itemPath;
- let repoID = this.props.repoID;
- seafileAPI.getShareLink(repoID, path).then((res) => {
- this.setState({
- isLoading: false,
- shareLinks: res.data.map(item => new ShareLink(item))
- });
- }).catch(error => {
- let errMessage = Utils.getErrorMsg(error);
- toaster.danger(errMessage);
- });
-
- if (isPro) {
- const { itemType, userPerm } = this.props;
- if (itemType == 'library') {
- let permissionOptions = Utils.getShareLinkPermissionList(itemType, userPerm, path);
- this.setState({
- permissionOptions: permissionOptions,
- currentPermission: permissionOptions[0],
- });
- } else {
- let getDirentInfoAPI;
- if (this.props.itemType === 'file') {
- getDirentInfoAPI = seafileAPI.getFileInfo(repoID, path);
- } else if (this.props.itemType === 'dir') {
- getDirentInfoAPI = seafileAPI.getDirInfo(repoID, path);
- }
- getDirentInfoAPI.then((res) => {
- let canEdit = res.data.can_edit;
- let permission = res.data.permission;
- let permissionOptions = Utils.getShareLinkPermissionList(this.props.itemType, permission, path, canEdit);
- this.setState({
- permissionOptions: permissionOptions,
- currentPermission: permissionOptions[0],
- });
- }).catch(error => {
- let errMessage = Utils.getErrorMsg(error);
- toaster.danger(errMessage);
- });
- }
- }
- }
-
- setExpType = (e) => {
- this.setState({
- expType: e.target.value
- });
- }
-
- onExpDateChanged = (value) => {
- this.setState({
- expDate: value
- });
- }
-
- onPasswordInputChecked = () => {
- this.setState({
- isShowPasswordInput: !this.state.isShowPasswordInput,
- password: '',
- passwdnew: '',
- errorInfo: ''
- });
- }
-
- togglePasswordVisible = () => {
- this.setState({
- isPasswordVisible: !this.state.isPasswordVisible
- });
- }
-
- generatePassword = () => {
- let val = Utils.generatePassword(shareLinkPasswordMinLength);
- this.setState({
- password: val,
- passwdnew: val
- });
- }
-
- inputPassword = (e) => {
- let passwd = e.target.value.trim();
- this.setState({password: passwd});
- }
-
- inputPasswordNew = (e) => {
- let passwd = e.target.value.trim();
- this.setState({passwdnew: passwd});
- }
-
- setPermission = (e) => {
- this.setState({currentPermission: e.target.value});
- }
-
- generateShareLink = () => {
- let isValid = this.validateParamsInput();
- if (isValid) {
- this.setState({errorInfo: ''});
- let { itemPath, repoID } = this.props;
- let { password, isExpireChecked, expType, expireDays, expDate } = this.state;
- let permissions;
- if (isPro) {
- const permissionDetails = Utils.getShareLinkPermissionObject(this.state.currentPermission).permissionDetails;
- permissions = JSON.stringify(permissionDetails);
- }
- let expirationTime = '';
- if (isExpireChecked) {
- if (expType == 'by-days') {
- expirationTime = moment().add(parseInt(expireDays), 'days').format();
- } else {
- expirationTime = expDate.format();
- }
- }
- seafileAPI.createMultiShareLink(repoID, itemPath, password, expirationTime, permissions).then((res) => {
- const links = this.state.shareLinks;
- const newLink = new ShareLink(res.data);
- links.unshift(newLink);
- this.setState({
- password: '',
- passwdnew: '',
- isShowPasswordInput: shareLinkForceUsePassword ? true : false,
- expireDays: this.defaultExpireDays,
- expDate: null,
- isExpireChecked: !this.isExpireDaysNoLimit,
- errorInfo: '',
- sharedLinkInfo: newLink,
- shareLinks: links
- });
- }).catch((error) => {
- let errMessage = Utils.getErrorMsg(error);
- toaster.danger(errMessage);
- });
- }
- }
-
- onExpireChecked = (e) => {
- this.setState({isExpireChecked: e.target.checked});
- }
-
- onExpireDaysChanged = (e) => {
- let day = e.target.value.trim();
- this.setState({expireDays: day});
- }
-
- validateParamsInput = () => {
- let { isShowPasswordInput, password, passwdnew, isExpireChecked, expType, expireDays, expDate } = this.state;
-
- // validate password
- if (isShowPasswordInput) {
- if (password.length === 0) {
- this.setState({errorInfo: gettext('Please enter a password.')});
- return false;
- }
- if (password.length < shareLinkPasswordMinLength) {
- this.setState({errorInfo: gettext('The password is too short.')});
- return false;
- }
- if (password !== passwdnew) {
- this.setState({errorInfo: gettext('Passwords don\'t match')});
- return false;
- }
- if (Utils.getStrengthLevel(password) < shareLinkPasswordStrengthLevel) {
- this.setState({errorInfo: gettext('The password is too weak. It should include at least {passwordStrengthLevel} of the following: number, upper letter, lower letter and other symbols.').replace('{passwordStrengthLevel}', shareLinkPasswordStrengthLevel)});
- return false;
- }
- }
-
- if (isExpireChecked) {
- if (expType == 'by-date') {
- if (!expDate) {
- this.setState({errorInfo: gettext('Please select an expiration time')});
- return false;
- }
- return true;
- }
-
- // by days
- let reg = /^\d+$/;
- if (!expireDays) {
- this.setState({errorInfo: gettext('Please enter days')});
- return false;
- }
- if (!reg.test(expireDays)) {
- this.setState({errorInfo: gettext('Please enter a non-negative integer')});
- return false;
- }
-
- expireDays = parseInt(expireDays);
- let minDays = shareLinkExpireDaysMin;
- let maxDays = shareLinkExpireDaysMax;
-
- if (minDays !== 0 && maxDays == 0) {
- if (expireDays < minDays) {
- this.setState({errorInfo: 'Please enter valid days'});
- return false;
- }
- }
-
- if (minDays === 0 && maxDays !== 0 ) {
- if (expireDays > maxDays) {
- this.setState({errorInfo: 'Please enter valid days'});
- return false;
- }
- }
-
- if (minDays !== 0 && maxDays !== 0) {
- if (expireDays < minDays || expireDays > maxDays) {
- this.setState({errorInfo: 'Please enter valid days'});
- return false;
- }
- }
-
- this.setState({expireDays: expireDays});
- }
-
- return true;
- }
-
- showLinkDetails = (link) => {
- this.setState({
- sharedLinkInfo: link
- });
- }
-
- updateLink = (link) => {
- const { shareLinks } = this.state;
- this.setState({
- sharedLinkInfo: link,
- shareLinks: shareLinks.map(item => item.token == link.token ? link : item)
- });
- }
-
- deleteLink = () => {
- const { sharedLinkInfo, shareLinks } = this.state;
- seafileAPI.deleteShareLink(sharedLinkInfo.token).then(() => {
- this.setState({
- sharedLinkInfo: null,
- shareLinks: shareLinks.filter(item => item.token !== sharedLinkInfo.token)
- });
- toaster.success(gettext('The link is deleted.'));
- }).catch((error) => {
- let errMessage = Utils.getErrorMsg(error);
- toaster.danger(errMessage);
- });
- }
-
- render() {
- if (this.state.isLoading) {
- return ;
- }
-
- const { userPerm } = this.props;
- const { isCustomPermission } = Utils.getUserPermission(userPerm);
- const { shareLinks, permissionOptions, sharedLinkInfo } = this.state;
-
- if (sharedLinkInfo) {
- return (
-
- );
- } else {
- return (
-
-
- {shareLinks.length > 0 && (
-
-
-
- {gettext('Link')} |
- {gettext('Permission')} |
- {gettext('Expiration')} |
- |
-
-
-
- {
- shareLinks.map((item, index) => {
- return (
-
- );
- })
- }
-
-
- )}
-
- );
- }
- }
-}
-
-class LinkItem extends React.Component {
-
- constructor(props) {
- super(props);
- this.state = {
- isItemOpVisible: false
- };
- }
-
- onMouseOver = () => {
- this.setState({
- isItemOpVisible: true
- });
- }
-
- onMouseOut = () => {
- this.setState({
- isItemOpVisible: false
- });
- }
-
- cutLink = (link) => {
- let length = link.length;
- return link.slice(0, 9) + '...' + link.slice(length-5);
- }
-
- viewDetails = (e) => {
- e.preventDefault();
- this.props.showLinkDetails(this.props.item);
- }
-
- render() {
- const { isItemOpVisible } = this.state;
- const { item, permissionOptions } = this.props;
- const { permissions, link, expire_date } = item;
- const currentPermission = Utils.getShareLinkPermissionStr(permissions);
- return (
-
- {this.cutLink(link)} |
-
- {(isPro && permissions) && (
- {}}
- />
- )}
- |
-
- {expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'}
- |
-
- {gettext('Details')}
- |
-
- );
- }
-}
-
-LinkItem.propTypes = {
- item: PropTypes.object.isRequired,
- permissionOptions: PropTypes.array,
- showLinkDetails : PropTypes.func.isRequired
-};
-
-class LinkDetails extends React.Component {
-
- constructor(props) {
- super(props);
- this.state = {
- storedPasswordVisible: false,
- isEditingExpiration: false,
- isExpirationEditIconShow: false,
- expType: 'by-days',
- expireDays: this.props.defaultExpireDays,
- expDate: null,
- isOpIconShown: false,
- isNoticeMessageShow: false,
- isSendLinkShown: false
- };
- }
-
- onCopySharedLink = () => {
- const { sharedLinkInfo } = this.props;
- copy(sharedLinkInfo.link);
- toaster.success(gettext('Share link is copied to the clipboard.'));
- this.props.closeShareDialog();
- }
-
- onCopyDownloadLink = () => {
- const { sharedLinkInfo } = this.props;
- copy(`${sharedLinkInfo.link}?dl=1`);
- toaster.success(gettext('Direct download link is copied to the clipboard.'));
- this.props.closeShareDialog();
- }
-
- toggleStoredPasswordVisible = () => {
- this.setState({
- storedPasswordVisible: !this.state.storedPasswordVisible
- });
- }
-
- handleMouseOverExpirationEditIcon = () => {
- this.setState({isExpirationEditIconShow: true});
- }
-
- handleMouseOutExpirationEditIcon = () => {
- this.setState({isExpirationEditIconShow: false});
- }
-
- editingExpirationToggle = () => {
- this.setState({isEditingExpiration: !this.state.isEditingExpiration});
- }
-
- setExpType = (e) => {
- this.setState({
- expType: e.target.value
- });
- }
-
- onExpDateChanged = (value) => {
- this.setState({
- expDate: value
- });
- }
-
- onExpireDaysChanged = (e) => {
- let day = e.target.value.trim();
- this.setState({expireDays: day});
- }
-
- updateExpiration = () => {
- const { sharedLinkInfo } = this.props;
- const { expType, expireDays, expDate } = this.state;
- let expirationTime = '';
- if (expType == 'by-days') {
- expirationTime = moment().add(parseInt(expireDays), 'days').format();
- } else {
- expirationTime = expDate.format();
- }
- seafileAPI.updateShareLink(sharedLinkInfo.token, '', expirationTime).then((res) => {
- this.setState({
- isEditingExpiration: false
- });
- this.props.updateLink(new ShareLink(res.data));
- }).catch((error) => {
- let errMessage = Utils.getErrorMsg(error);
- toaster.danger(errMessage);
- });
- }
-
- handleMouseOver = () => {
- this.setState({isOpIconShown: true});
- }
-
- handleMouseOut = () => {
- this.setState({isOpIconShown: false});
- }
-
- changePerm = (permission) => {
- const { sharedLinkInfo } = this.props;
- const { permissionDetails } = Utils.getShareLinkPermissionObject(permission);
- seafileAPI.updateShareLink(sharedLinkInfo.token, JSON.stringify(permissionDetails)).then((res) => {
- this.props.updateLink(new ShareLink(res.data));
- }).catch((error) => {
- let errMessage = Utils.getErrorMsg(error);
- toaster.danger(errMessage);
- });
- }
-
- onNoticeMessageToggle = () => {
- this.setState({isNoticeMessageShow: !this.state.isNoticeMessageShow});
- }
-
- toggleSendLink = () => {
- this.setState({ isSendLinkShown: !this.state.isSendLinkShown });
- }
-
- render() {
- const { sharedLinkInfo, permissionOptions } = this.props;
- const { isOpIconShown } = this.state;
- const currentPermission = Utils.getShareLinkPermissionStr(sharedLinkInfo.permissions);
- return (
-
-
-
- - {gettext('Link:')}
- -
-
-
- {!sharedLinkInfo.is_dir && sharedLinkInfo.permissions.can_download && ( // just for file
- <>
- - {gettext('Direct Download Link:')}
- -
-
-
- >
- )}
- {sharedLinkInfo.password && (
- <>
- - {gettext('Password:')}
- -
-
-
-
-
-
- >
- )}
- {sharedLinkInfo.expire_date && (
- <>
- - {gettext('Expiration Date:')}
- {!this.state.isEditingExpiration &&
- -
- {moment(sharedLinkInfo.expire_date).format('YYYY-MM-DD HH:mm:ss')}
- {this.state.isExpirationEditIconShow && (
-
-
- )}
-
- }
- {this.state.isEditingExpiration &&
- -
-
-
-
-
-
-
-
-
- }
- >
- )}
- {(isPro && sharedLinkInfo.permissions) && (
- <>
- - {gettext('Permission:')}
- -
-
-
- >
- )}
-
- {(canSendShareLinkEmail && !this.state.isSendLinkShown && !this.state.isNoticeMessageShow) &&
-
- }
- {this.state.isSendLinkShown &&
-
- }
- {(!this.state.isSendLinkShown && !this.state.isNoticeMessageShow) &&
-
- }
- {this.state.isNoticeMessageShow &&
-
-
{gettext('Are you sure you want to delete the share link?')}
-
{gettext('If the share link is deleted, no one will be able to access it any more.')}
-
{' '}
-
-
- }
-
- );
- }
-}
-
-LinkDetails.propTypes = {
- sharedLinkInfo: PropTypes.object.isRequired,
- permissionOptions: PropTypes.array.isRequired,
- defaultExpireDays: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number
- ]),
- showLinkDetails: PropTypes.func.isRequired,
- updateLink: PropTypes.func.isRequired,
- deleteLink: PropTypes.func.isRequired,
- closeShareDialog: PropTypes.func.isRequired
-};
-
-ShareLinkPanel.propTypes = propTypes;
-
-export default ShareLinkPanel;
diff --git a/frontend/src/components/share-link-panel/index.js b/frontend/src/components/share-link-panel/index.js
new file mode 100644
index 0000000000..dbe5610c54
--- /dev/null
+++ b/frontend/src/components/share-link-panel/index.js
@@ -0,0 +1,225 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault } from '../../utils/constants';
+import { seafileAPI } from '../../utils/seafile-api';
+import { Utils } from '../../utils/utils';
+import ShareLink from '../../models/share-link';
+import toaster from '../toast';
+import Loading from '../loading';
+import LinkDetails from './link-details';
+import LinkItem from './link-item';
+import LinkCreation from './link-creation';
+
+const propTypes = {
+ itemPath: PropTypes.string.isRequired,
+ repoID: PropTypes.string.isRequired,
+ closeShareDialog: PropTypes.func.isRequired,
+ userPerm: PropTypes.string,
+ itemType: PropTypes.string
+};
+
+class ShareLinkPanel extends React.Component {
+
+ constructor(props) {
+ super(props);
+
+ this.isExpireDaysNoLimit = (shareLinkExpireDaysMin === 0 && shareLinkExpireDaysMax === 0 && shareLinkExpireDaysDefault == 0);
+ this.defaultExpireDays = this.isExpireDaysNoLimit ? '' : shareLinkExpireDaysDefault;
+
+ this.state = {
+ isLoading: true,
+ mode: 'listLinks',
+ sharedLinkInfo: null,
+ shareLinks: [],
+ permissionOptions: [],
+ currentPermission: ''
+ };
+ }
+
+ componentDidMount() {
+ let path = this.props.itemPath;
+ let repoID = this.props.repoID;
+ seafileAPI.getShareLink(repoID, path).then((res) => {
+ this.setState({
+ isLoading: false,
+ shareLinks: res.data.map(item => new ShareLink(item))
+ });
+ }).catch(error => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+
+ if (isPro) {
+ const { itemType, userPerm } = this.props;
+ if (itemType == 'library') {
+ let permissionOptions = Utils.getShareLinkPermissionList(itemType, userPerm, path);
+ this.setState({
+ permissionOptions: permissionOptions,
+ currentPermission: permissionOptions[0],
+ });
+ } else {
+ let getDirentInfoAPI;
+ if (this.props.itemType === 'file') {
+ getDirentInfoAPI = seafileAPI.getFileInfo(repoID, path);
+ } else if (this.props.itemType === 'dir') {
+ getDirentInfoAPI = seafileAPI.getDirInfo(repoID, path);
+ }
+ getDirentInfoAPI.then((res) => {
+ let canEdit = res.data.can_edit;
+ let permission = res.data.permission;
+ let permissionOptions = Utils.getShareLinkPermissionList(this.props.itemType, permission, path, canEdit);
+ this.setState({
+ permissionOptions: permissionOptions,
+ currentPermission: permissionOptions[0],
+ });
+ }).catch(error => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+ }
+ }
+
+ showLinkDetails = (link) => {
+ this.setState({
+ sharedLinkInfo: link,
+ mode: link ? 'displayLinkDetails' : ''
+ });
+ }
+
+ updateLink = (link) => {
+ const { shareLinks } = this.state;
+ this.setState({
+ sharedLinkInfo: link,
+ shareLinks: shareLinks.map(item => item.token == link.token ? link : item)
+ });
+ }
+
+ deleteLink = () => {
+ const { sharedLinkInfo, shareLinks } = this.state;
+ seafileAPI.deleteShareLink(sharedLinkInfo.token).then(() => {
+ this.setState({
+ mode: '',
+ sharedLinkInfo: null,
+ shareLinks: shareLinks.filter(item => item.token !== sharedLinkInfo.token)
+ });
+ toaster.success(gettext('The link is deleted.'));
+ }).catch((error) => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+
+ updateAfterCreation = (newData) => {
+ const { mode, shareLinks: links } = this.state;
+ if (mode == 'singleLinkCreation') {
+ links.unshift(newData);
+ this.setState({
+ mode: 'displayLinkDetails',
+ sharedLinkInfo: newData,
+ shareLinks: links
+ });
+ } else { // 'linksCreation'
+ this.setState({
+ mode: '',
+ shareLinks: newData.concat(links)
+ });
+ }
+ }
+
+ setMode = (mode) => {
+ this.setState({ mode: mode });
+ }
+
+ render() {
+ if (this.state.isLoading) {
+ return ;
+ }
+
+ const { repoID, itemPath, userPerm } = this.props;
+ const { mode, shareLinks, sharedLinkInfo, permissionOptions, currentPermission } = this.state;
+
+ switch (mode) {
+ case 'displayLinkDetails':
+ return (
+
+ );
+ case 'singleLinkCreation':
+ return (
+
+ );
+ case 'linksCreation':
+ return (
+
+ );
+ default:
+ return (
+
+
+
{gettext('Share Link')}
+
+
+
+
+
+ {shareLinks.length > 0 && (
+
+
+
+ {gettext('Link')} |
+ {gettext('Permission')} |
+ {gettext('Expiration')} |
+ |
+
+
+
+ {
+ shareLinks.map((item, index) => {
+ return (
+
+ );
+ })
+ }
+
+
+ )}
+
+ );
+ }
+ }
+}
+
+ShareLinkPanel.propTypes = propTypes;
+
+export default ShareLinkPanel;
diff --git a/frontend/src/components/share-link-panel/link-creation.js b/frontend/src/components/share-link-panel/link-creation.js
new file mode 100644
index 0000000000..7283e86aee
--- /dev/null
+++ b/frontend/src/components/share-link-panel/link-creation.js
@@ -0,0 +1,342 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, Alert } from 'reactstrap';
+import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, shareLinkForceUsePassword, shareLinkPasswordMinLength, shareLinkPasswordStrengthLevel } from '../../utils/constants';
+import { seafileAPI } from '../../utils/seafile-api';
+import { Utils } from '../../utils/utils';
+import ShareLink from '../../models/share-link';
+import toaster from '../toast';
+import SetLinkExpiration from '../set-link-expiration';
+
+const propTypes = {
+ itemPath: PropTypes.string.isRequired,
+ repoID: PropTypes.string.isRequired,
+ userPerm: PropTypes.string,
+ type: PropTypes.string.isRequired,
+ permissionOptions: PropTypes.array.isRequired,
+ currentPermission: PropTypes.string.isRequired,
+ updateAfterCreation: PropTypes.func.isRequired,
+ setMode: PropTypes.func.isRequired,
+};
+
+const inputWidth = Utils.isDesktop() ? 250 : 210;
+
+class LinkCreation extends React.Component {
+
+ constructor(props) {
+ super(props);
+
+ this.isExpireDaysNoLimit = (shareLinkExpireDaysMin === 0 && shareLinkExpireDaysMax === 0 && shareLinkExpireDaysDefault == 0);
+ this.defaultExpireDays = this.isExpireDaysNoLimit ? '' : shareLinkExpireDaysDefault;
+
+ this.state = {
+ linkAmount: '',
+ isShowPasswordInput: shareLinkForceUsePassword ? true : false,
+ isPasswordVisible: false,
+ isExpireChecked: !this.isExpireDaysNoLimit,
+ expType: 'by-days',
+ expireDays: this.defaultExpireDays,
+ expDate: null,
+ password: '',
+ passwdnew: '',
+ errorInfo: '',
+ currentPermission: props.currentPermission
+ };
+ }
+
+ setExpType = (e) => {
+ this.setState({
+ expType: e.target.value
+ });
+ }
+
+ onExpDateChanged = (value) => {
+ this.setState({
+ expDate: value
+ });
+ }
+
+ onPasswordInputChecked = () => {
+ this.setState({
+ isShowPasswordInput: !this.state.isShowPasswordInput,
+ password: '',
+ passwdnew: '',
+ errorInfo: ''
+ });
+ }
+
+ togglePasswordVisible = () => {
+ this.setState({
+ isPasswordVisible: !this.state.isPasswordVisible
+ });
+ }
+
+ generatePassword = () => {
+ let val = Utils.generatePassword(shareLinkPasswordMinLength);
+ this.setState({
+ password: val,
+ passwdnew: val
+ });
+ }
+
+ inputPassword = (e) => {
+ let passwd = e.target.value.trim();
+ this.setState({password: passwd});
+ }
+
+ inputPasswordNew = (e) => {
+ let passwd = e.target.value.trim();
+ this.setState({passwdnew: passwd});
+ }
+
+ setPermission = (e) => {
+ this.setState({currentPermission: e.target.value});
+ }
+
+ generateShareLink = () => {
+ let isValid = this.validateParamsInput();
+ if (isValid) {
+ this.setState({errorInfo: ''});
+ let { type, itemPath, repoID } = this.props;
+ let { linkAmount, isShowPasswordInput, password, isExpireChecked, expType, expireDays, expDate } = this.state;
+ let permissions;
+ if (isPro) {
+ const permissionDetails = Utils.getShareLinkPermissionObject(this.state.currentPermission).permissionDetails;
+ permissions = JSON.stringify(permissionDetails);
+ }
+ let expirationTime = '';
+ if (isExpireChecked) {
+ if (expType == 'by-days') {
+ expirationTime = moment().add(parseInt(expireDays), 'days').format();
+ } else {
+ expirationTime = expDate.format();
+ }
+ }
+
+ let request;
+ if (type == 'batch') {
+ const autoGeneratePassword = shareLinkForceUsePassword || isShowPasswordInput;
+ request = seafileAPI.batchCreateMultiShareLink(repoID, itemPath, linkAmount, autoGeneratePassword, expirationTime, permissions);
+ } else {
+ request = seafileAPI.createMultiShareLink(repoID, itemPath, password, expirationTime, permissions);
+ }
+
+ request.then((res) => {
+ if (type == 'batch') {
+ const newLinks = res.data.map(item => new ShareLink(item));
+ this.props.updateAfterCreation(newLinks);
+ } else {
+ const newLink = new ShareLink(res.data);
+ this.props.updateAfterCreation(newLink);
+ }
+ }).catch((error) => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+ }
+
+ onExpireChecked = (e) => {
+ this.setState({isExpireChecked: e.target.checked});
+ }
+
+ onExpireDaysChanged = (e) => {
+ let day = e.target.value.trim();
+ this.setState({expireDays: day});
+ }
+
+ validateParamsInput = () => {
+ const { type } = this.props;
+ let { linkAmount, isShowPasswordInput, password, passwdnew, isExpireChecked, expType, expireDays, expDate } = this.state;
+
+ if (type == 'batch') {
+ if (!Number.isInteger(parseInt(linkAmount)) || parseInt(linkAmount) <= 1) {
+ this.setState({errorInfo: gettext('Please enter an integer bigger than 1 as number of links.')});
+ return false;
+ }
+ }
+
+ if (isShowPasswordInput) {
+ if (password.length === 0) {
+ this.setState({errorInfo: gettext('Please enter a password.')});
+ return false;
+ }
+ if (password.length < shareLinkPasswordMinLength) {
+ this.setState({errorInfo: gettext('The password is too short.')});
+ return false;
+ }
+ if (password !== passwdnew) {
+ this.setState({errorInfo: gettext('Passwords don\'t match')});
+ return false;
+ }
+ if (Utils.getStrengthLevel(password) < shareLinkPasswordStrengthLevel) {
+ this.setState({errorInfo: gettext('The password is too weak. It should include at least {passwordStrengthLevel} of the following: number, upper letter, lower letter and other symbols.').replace('{passwordStrengthLevel}', shareLinkPasswordStrengthLevel)});
+ return false;
+ }
+ }
+
+ if (isExpireChecked) {
+ if (expType == 'by-date') {
+ if (!expDate) {
+ this.setState({errorInfo: gettext('Please select an expiration time')});
+ return false;
+ }
+ return true;
+ }
+
+ // by days
+ let reg = /^\d+$/;
+ if (!expireDays) {
+ this.setState({errorInfo: gettext('Please enter days')});
+ return false;
+ }
+ if (!reg.test(expireDays)) {
+ this.setState({errorInfo: gettext('Please enter a non-negative integer')});
+ return false;
+ }
+
+ expireDays = parseInt(expireDays);
+ let minDays = shareLinkExpireDaysMin;
+ let maxDays = shareLinkExpireDaysMax;
+
+ if (minDays !== 0 && maxDays == 0) {
+ if (expireDays < minDays) {
+ this.setState({errorInfo: 'Please enter valid days'});
+ return false;
+ }
+ }
+
+ if (minDays === 0 && maxDays !== 0 ) {
+ if (expireDays > maxDays) {
+ this.setState({errorInfo: 'Please enter valid days'});
+ return false;
+ }
+ }
+
+ if (minDays !== 0 && maxDays !== 0) {
+ if (expireDays < minDays || expireDays > maxDays) {
+ this.setState({errorInfo: 'Please enter valid days'});
+ return false;
+ }
+ }
+
+ this.setState({expireDays: expireDays});
+ }
+
+ return true;
+ }
+
+ onLinkAmountChange = (e) => {
+ this.setState({
+ linkAmount: e.target.value
+ });
+ }
+
+ goBack = () => {
+ this.props.setMode('');
+ }
+
+ render() {
+ const { userPerm, type, permissionOptions } = this.props;
+ const { isCustomPermission } = Utils.getUserPermission(userPerm);
+
+ return (
+
+
+
+
+ {type == 'batch' ? gettext('Generate links in batch') : gettext('Generate Link')}
+
+
+
+ );
+ }
+}
+
+LinkCreation.propTypes = propTypes;
+
+export default LinkCreation;
diff --git a/frontend/src/components/share-link-panel/link-details.js b/frontend/src/components/share-link-panel/link-details.js
new file mode 100644
index 0000000000..d337486c38
--- /dev/null
+++ b/frontend/src/components/share-link-panel/link-details.js
@@ -0,0 +1,269 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import copy from 'copy-to-clipboard';
+import { Button } from 'reactstrap';
+import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, canSendShareLinkEmail } from '../../utils/constants';
+import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor';
+import { seafileAPI } from '../../utils/seafile-api';
+import { Utils } from '../../utils/utils';
+import ShareLink from '../../models/share-link';
+import toaster from '../toast';
+import SendLink from '../send-link';
+import SharedLink from '../shared-link';
+import SetLinkExpiration from '../set-link-expiration';
+
+const propTypes = {
+ sharedLinkInfo: PropTypes.object.isRequired,
+ permissionOptions: PropTypes.array.isRequired,
+ defaultExpireDays: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number
+ ]),
+ showLinkDetails: PropTypes.func.isRequired,
+ updateLink: PropTypes.func.isRequired,
+ deleteLink: PropTypes.func.isRequired,
+ closeShareDialog: PropTypes.func.isRequired
+};
+
+class LinkDetails extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ storedPasswordVisible: false,
+ isEditingExpiration: false,
+ isExpirationEditIconShow: false,
+ expType: 'by-days',
+ expireDays: this.props.defaultExpireDays,
+ expDate: null,
+ isOpIconShown: false,
+ isNoticeMessageShow: false,
+ isSendLinkShown: false
+ };
+ }
+
+ onCopySharedLink = () => {
+ const { sharedLinkInfo } = this.props;
+ copy(sharedLinkInfo.link);
+ toaster.success(gettext('Share link is copied to the clipboard.'));
+ this.props.closeShareDialog();
+ }
+
+ onCopyDownloadLink = () => {
+ const { sharedLinkInfo } = this.props;
+ copy(`${sharedLinkInfo.link}?dl=1`);
+ toaster.success(gettext('Direct download link is copied to the clipboard.'));
+ this.props.closeShareDialog();
+ }
+
+ toggleStoredPasswordVisible = () => {
+ this.setState({
+ storedPasswordVisible: !this.state.storedPasswordVisible
+ });
+ }
+
+ handleMouseOverExpirationEditIcon = () => {
+ this.setState({isExpirationEditIconShow: true});
+ }
+
+ handleMouseOutExpirationEditIcon = () => {
+ this.setState({isExpirationEditIconShow: false});
+ }
+
+ editingExpirationToggle = () => {
+ this.setState({isEditingExpiration: !this.state.isEditingExpiration});
+ }
+
+ setExpType = (e) => {
+ this.setState({
+ expType: e.target.value
+ });
+ }
+
+ onExpDateChanged = (value) => {
+ this.setState({
+ expDate: value
+ });
+ }
+
+ onExpireDaysChanged = (e) => {
+ let day = e.target.value.trim();
+ this.setState({expireDays: day});
+ }
+
+ updateExpiration = () => {
+ const { sharedLinkInfo } = this.props;
+ const { expType, expireDays, expDate } = this.state;
+ let expirationTime = '';
+ if (expType == 'by-days') {
+ expirationTime = moment().add(parseInt(expireDays), 'days').format();
+ } else {
+ expirationTime = expDate.format();
+ }
+ seafileAPI.updateShareLink(sharedLinkInfo.token, '', expirationTime).then((res) => {
+ this.setState({
+ isEditingExpiration: false
+ });
+ this.props.updateLink(new ShareLink(res.data));
+ }).catch((error) => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+
+ handleMouseOver = () => {
+ this.setState({isOpIconShown: true});
+ }
+
+ handleMouseOut = () => {
+ this.setState({isOpIconShown: false});
+ }
+
+ changePerm = (permission) => {
+ const { sharedLinkInfo } = this.props;
+ const { permissionDetails } = Utils.getShareLinkPermissionObject(permission);
+ seafileAPI.updateShareLink(sharedLinkInfo.token, JSON.stringify(permissionDetails)).then((res) => {
+ this.props.updateLink(new ShareLink(res.data));
+ }).catch((error) => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+
+ onNoticeMessageToggle = () => {
+ this.setState({isNoticeMessageShow: !this.state.isNoticeMessageShow});
+ }
+
+ toggleSendLink = () => {
+ this.setState({ isSendLinkShown: !this.state.isSendLinkShown });
+ }
+
+ goBack = () => {
+ this.props.showLinkDetails(null);
+ }
+
+ render() {
+ const { sharedLinkInfo, permissionOptions } = this.props;
+ const { isOpIconShown } = this.state;
+ const currentPermission = Utils.getShareLinkPermissionStr(sharedLinkInfo.permissions);
+ return (
+
+
+
+ - {gettext('Link:')}
+ -
+
+
+ {!sharedLinkInfo.is_dir && sharedLinkInfo.permissions.can_download && ( // just for file
+ <>
+ - {gettext('Direct Download Link:')}
+ -
+
+
+ >
+ )}
+ {sharedLinkInfo.password && (
+ <>
+ - {gettext('Password:')}
+ -
+
+
+
+
+
+ >
+ )}
+ {sharedLinkInfo.expire_date && (
+ <>
+ - {gettext('Expiration Date:')}
+ {!this.state.isEditingExpiration &&
+ -
+ {moment(sharedLinkInfo.expire_date).format('YYYY-MM-DD HH:mm:ss')}
+ {this.state.isExpirationEditIconShow && (
+
+
+ )}
+
+ }
+ {this.state.isEditingExpiration &&
+ -
+
+
+
+
+
+
+
+
+ }
+ >
+ )}
+ {(isPro && sharedLinkInfo.permissions) && (
+ <>
+ - {gettext('Permission:')}
+ -
+
+
+ >
+ )}
+
+ {(canSendShareLinkEmail && !this.state.isSendLinkShown && !this.state.isNoticeMessageShow) &&
+
+ }
+ {this.state.isSendLinkShown &&
+
+ }
+ {(!this.state.isSendLinkShown && !this.state.isNoticeMessageShow) &&
+
+ }
+ {this.state.isNoticeMessageShow &&
+
+
{gettext('Are you sure you want to delete the share link?')}
+
{gettext('If the share link is deleted, no one will be able to access it any more.')}
+
{' '}
+
+
+ }
+
+ );
+ }
+}
+
+LinkDetails.propTypes = propTypes;
+
+export default LinkDetails;
diff --git a/frontend/src/components/share-link-panel/link-item.js b/frontend/src/components/share-link-panel/link-item.js
new file mode 100644
index 0000000000..1a41fc127e
--- /dev/null
+++ b/frontend/src/components/share-link-panel/link-item.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import { isPro, gettext } from '../../utils/constants';
+import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor';
+import { Utils } from '../../utils/utils';
+
+const propTypes = {
+ item: PropTypes.object.isRequired,
+ permissionOptions: PropTypes.array,
+ showLinkDetails : PropTypes.func.isRequired
+};
+
+class LinkItem extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isItemOpVisible: false
+ };
+ }
+
+ onMouseOver = () => {
+ this.setState({
+ isItemOpVisible: true
+ });
+ }
+
+ onMouseOut = () => {
+ this.setState({
+ isItemOpVisible: false
+ });
+ }
+
+ cutLink = (link) => {
+ let length = link.length;
+ return link.slice(0, 9) + '...' + link.slice(length-5);
+ }
+
+ viewDetails = (e) => {
+ e.preventDefault();
+ this.props.showLinkDetails(this.props.item);
+ }
+
+ render() {
+ const { isItemOpVisible } = this.state;
+ const { item, permissionOptions } = this.props;
+ const { permissions, link, expire_date } = item;
+ const currentPermission = Utils.getShareLinkPermissionStr(permissions);
+ return (
+
+ {this.cutLink(link)} |
+
+ {(isPro && permissions) && (
+ {}}
+ />
+ )}
+ |
+
+ {expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'}
+ |
+
+ {gettext('Details')}
+ |
+
+ );
+ }
+}
+
+LinkItem.propTypes = propTypes;
+
+export default LinkItem;
diff --git a/seahub/api2/endpoints/multi_share_links.py b/seahub/api2/endpoints/multi_share_links.py
index bb7178fa09..773eaae8e2 100644
--- a/seahub/api2/endpoints/multi_share_links.py
+++ b/seahub/api2/endpoints/multi_share_links.py
@@ -202,3 +202,213 @@ class MultiShareLinks(APIView):
link_info = get_share_link_info(fs)
return Response(link_info)
+
+
+class MultiShareLinksBatch(APIView):
+
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated, CanGenerateShareLink)
+ throttle_classes = (UserRateThrottle,)
+
+ def post(self, request):
+ """ Create multi share link.
+ Permission checking:
+ 1. default(NOT guest) user;
+ """
+
+ # argument check
+ repo_id = request.data.get('repo_id', None)
+ if not repo_id:
+ error_msg = 'repo_id invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ path = request.data.get('path', None)
+ if not path:
+ error_msg = 'path invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ share_link_num = request.data.get('number')
+ if not share_link_num:
+ error_msg = 'number invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ try:
+ share_link_num = int(share_link_num)
+ except ValueError:
+ error_msg = 'number invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ if share_link_num <= 0:
+ error_msg = 'number invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ auto_generate_password = request.data.get('auto_generate_password')
+ if not auto_generate_password:
+ error_msg = 'auto_generate_password invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ auto_generate_password = auto_generate_password.lower()
+ if auto_generate_password not in ('true', 'false'):
+ error_msg = 'auto_generate_password invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ auto_generate_password = auto_generate_password == 'true'
+
+ if config.SHARE_LINK_FORCE_USE_PASSWORD and not auto_generate_password:
+ error_msg = _('Password is required.')
+ return api_error(status.HTTP_400_BAD_REQUEST, 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 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)
+
+ 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 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)
+
+ else:
+ if SHARE_LINK_EXPIRE_DAYS_DEFAULT > 0:
+ expire_date = timezone.now() + relativedelta(days=SHARE_LINK_EXPIRE_DAYS_DEFAULT)
+
+ try:
+ perm = check_permissions_arg(request)
+ except Exception:
+ error_msg = 'permissions 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)
+
+ if path != '/':
+ dirent = seafile_api.get_dirent_by_path(repo_id, path)
+ if not dirent:
+ error_msg = 'Dirent %s not found.' % path
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # permission check
+ if repo.encrypted:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ username = request.user.username
+ repo_folder_permission = seafile_api.check_permission_by_path(repo_id, path, username)
+ if parse_repo_perm(repo_folder_permission).can_generate_share_link is False:
+ 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)
+
+ 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)
+
+ # can_upload requires rw repo permission
+ if perm == FileShare.PERM_VIEW_DL_UPLOAD and \
+ repo_folder_permission != PERMISSION_READ_WRITE:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ if path != '/':
+ s_type = 'd' if stat.S_ISDIR(dirent.mode) else 'f'
+ if s_type == 'f':
+ file_name = os.path.basename(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)
+ else:
+ s_type = 'd'
+
+ # create share link
+ org_id = request.user.org.org_id if is_org_context(request) else None
+
+ def generate_password():
+ import random
+ import string
+ password_length = 8
+ characters = string.ascii_letters + string.digits + string.punctuation
+ password = ''.join(random.choices(characters, k=password_length))
+ return password
+
+ created_share_links = []
+ for i in range(share_link_num):
+ password = generate_password() if auto_generate_password else None
+ if s_type == 'f':
+ fs = FileShare.objects.create_file_link(username, repo_id, path,
+ password, expire_date,
+ permission=perm, org_id=org_id)
+
+ elif s_type == 'd':
+ fs = FileShare.objects.create_dir_link(username, repo_id, path,
+ password, expire_date,
+ permission=perm, org_id=org_id)
+
+ created_share_links.append(fs)
+
+ result = []
+ for fs in created_share_links:
+ link_info = get_share_link_info(fs)
+ link_info['repo_folder_permission'] = repo_folder_permission
+ result.append(link_info)
+
+ return Response(result)
diff --git a/seahub/urls.py b/seahub/urls.py
index 75ec02afb0..c5e4bee605 100644
--- a/seahub/urls.py
+++ b/seahub/urls.py
@@ -41,8 +41,10 @@ from seahub.api2.endpoints.search_group import SearchGroup
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink, \
ShareLinkOnlineOfficeLock, ShareLinkDirents, ShareLinkSaveFileToRepo, \
ShareLinkUpload, ShareLinkUploadDone, ShareLinkSaveItemsToRepo, \
- ShareLinkRepoTags, ShareLinkRepoTagsTaggedFiles, ShareLinksCleanInvalid
-from seahub.api2.endpoints.multi_share_links import MultiShareLinks
+ ShareLinkRepoTags, ShareLinkRepoTagsTaggedFiles, \
+ ShareLinksCleanInvalid
+from seahub.api2.endpoints.multi_share_links import MultiShareLinks, \
+ MultiShareLinksBatch
from seahub.api2.endpoints.shared_folders import SharedFolders
from seahub.api2.endpoints.shared_repos import SharedRepos, SharedRepo
from seahub.api2.endpoints.upload_links import UploadLinks, UploadLink, \
@@ -346,6 +348,7 @@ urlpatterns = [
## user::shared-download-links
url(r'^api/v2.1/share-links/$', ShareLinks.as_view(), name='api-v2.1-share-links'),
url(r'^api/v2.1/multi-share-links/$', MultiShareLinks.as_view(), name='api-v2.1-multi-share-links'),
+ url(r'^api/v2.1/multi-share-links/batch/$', MultiShareLinksBatch.as_view(), name='api-v2.1-multi-share-links-batch'),
url(r'^api/v2.1/share-links/clean-invalid/$', ShareLinksCleanInvalid.as_view(), name='api-v2.1-share-links-clean-invalid'),
url(r'^api/v2.1/share-links/(?P[a-f0-9]+)/$', ShareLink.as_view(), name='api-v2.1-share-link'),
url(r'^api/v2.1/share-links/(?P[a-f0-9]+)/save-file-to-repo/$', ShareLinkSaveFileToRepo.as_view(), name='api-v2.1-share-link-save-file-to-repo'),