2023-04-04 16:12:53 +08:00
import React , { Fragment } from 'react' ;
import PropTypes from 'prop-types' ;
2024-10-21 11:29:17 +08:00
import dayjs from 'dayjs' ;
2023-04-04 16:12:53 +08:00
import { Button , Form , FormGroup , Label , Input , InputGroup , InputGroupAddon , Alert } from 'reactstrap' ;
2024-07-19 18:12:35 +08:00
import { gettext , shareLinkExpireDaysMin , shareLinkExpireDaysMax , shareLinkExpireDaysDefault , shareLinkForceUsePassword , shareLinkPasswordMinLength , shareLinkPasswordStrengthLevel , isEmailConfigured } from '../../utils/constants' ;
2023-04-04 16:12:53 +08:00
import { seafileAPI } from '../../utils/seafile-api' ;
2024-07-19 18:12:35 +08:00
import { shareLinkAPI } from '../../utils/share-link-api' ;
2023-04-04 16:12:53 +08:00
import { Utils } from '../../utils/utils' ;
import ShareLink from '../../models/share-link' ;
import toaster from '../toast' ;
import SetLinkExpiration from '../set-link-expiration' ;
2024-07-19 18:12:35 +08:00
import UserSelect from '../user-select' ;
2023-04-04 16:12:53 +08:00
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 ;
2023-08-22 17:35:16 +08:00
const SHARE _LINK _MAX _NUMBER = 200 ;
2023-04-04 16:12:53 +08:00
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 : '' ,
2024-11-20 20:37:46 +08:00
newPassword : '' ,
2023-04-04 16:12:53 +08:00
errorInfo : '' ,
2024-07-19 18:12:35 +08:00
currentPermission : props . currentPermission ,
currentScope : 'all_users' ,
selectedOption : null ,
inputEmails : ''
2023-04-04 16:12:53 +08:00
} ;
}
setExpType = ( e ) => {
this . setState ( {
expType : e . target . value
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onExpDateChanged = ( value ) => {
this . setState ( {
expDate : value
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onPasswordInputChecked = ( ) => {
this . setState ( {
isShowPasswordInput : ! this . state . isShowPasswordInput ,
password : '' ,
2024-11-20 20:37:46 +08:00
newPassword : '' ,
2023-04-04 16:12:53 +08:00
errorInfo : ''
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
togglePasswordVisible = ( ) => {
this . setState ( {
isPasswordVisible : ! this . state . isPasswordVisible
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
generatePassword = ( ) => {
let val = Utils . generatePassword ( shareLinkPasswordMinLength ) ;
this . setState ( {
password : val ,
2024-11-20 20:37:46 +08:00
newPassword : val
2023-04-04 16:12:53 +08:00
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
inputPassword = ( e ) => {
let passwd = e . target . value . trim ( ) ;
2024-07-18 11:58:42 +08:00
this . setState ( { password : passwd } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
inputPasswordNew = ( e ) => {
let passwd = e . target . value . trim ( ) ;
2024-11-20 20:37:46 +08:00
this . setState ( { newPassword : passwd } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
setPermission = ( e ) => {
2024-07-18 11:58:42 +08:00
this . setState ( { currentPermission : e . target . value } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
generateShareLink = ( ) => {
let isValid = this . validateParamsInput ( ) ;
if ( isValid ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : '' } ) ;
2023-04-04 16:12:53 +08:00
let { type , itemPath , repoID } = this . props ;
let { linkAmount , isShowPasswordInput , password , isExpireChecked , expType , expireDays , expDate } = this . state ;
2024-07-11 14:28:00 +08:00
const permissionDetails = Utils . getShareLinkPermissionObject ( this . state . currentPermission ) . permissionDetails ;
2023-04-04 16:12:53 +08:00
let permissions ;
2024-07-11 14:28:00 +08:00
permissions = JSON . stringify ( permissionDetails ) ;
2023-04-04 16:12:53 +08:00
let expirationTime = '' ;
if ( isExpireChecked ) {
2024-07-19 18:12:35 +08:00
if ( expType === 'by-days' ) {
2024-10-21 11:29:17 +08:00
expirationTime = dayjs ( ) . add ( parseInt ( expireDays ) , 'days' ) . format ( ) ;
2023-04-04 16:12:53 +08:00
} else {
expirationTime = expDate . format ( ) ;
}
}
let request ;
2024-07-19 18:12:35 +08:00
let users ;
if ( type === 'batch' ) {
2023-04-04 16:12:53 +08:00
const autoGeneratePassword = shareLinkForceUsePassword || isShowPasswordInput ;
request = seafileAPI . batchCreateMultiShareLink ( repoID , itemPath , linkAmount , autoGeneratePassword , expirationTime , permissions ) ;
} else {
2024-07-19 18:12:35 +08:00
const { currentScope , selectedOption , inputEmails } = this . state ;
2024-07-23 23:08:53 +08:00
if ( currentScope === 'specific_users' && selectedOption ) {
2024-07-19 18:12:35 +08:00
users = selectedOption . map ( ( item , index ) => item . email ) ;
}
if ( currentScope === 'specific_emails' && inputEmails ) {
users = inputEmails ;
}
request = shareLinkAPI . createMultiShareLink ( repoID , itemPath , password , expirationTime , permissions , currentScope , users ) ;
2023-04-04 16:12:53 +08:00
}
request . then ( ( res ) => {
2024-07-19 18:12:35 +08:00
if ( type === 'batch' ) {
2023-04-04 16:12:53 +08:00
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 ) => {
2024-03-18 11:28:01 +08:00
let resp _data = error . response . data ;
let errMessage = resp _data && resp _data [ 'error_msg' ] ;
if ( errMessage === 'Folder permission denied.' ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'Share links cannot be generated because "Invisible", "Online Read-Write" or "Online Read-Only" is set for you on some folder(s) in the library.' ) } ) ;
2024-03-18 11:28:01 +08:00
} else {
let errMessage = Utils . getErrorMsg ( error ) ;
toaster . danger ( errMessage ) ;
}
2023-04-04 16:12:53 +08:00
} ) ;
}
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onExpireChecked = ( e ) => {
2024-07-18 11:58:42 +08:00
this . setState ( { isExpireChecked : e . target . checked } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onExpireDaysChanged = ( e ) => {
let day = e . target . value . trim ( ) ;
2024-07-18 11:58:42 +08:00
this . setState ( { expireDays : day } ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
validateParamsInput = ( ) => {
const { type } = this . props ;
2024-11-20 20:37:46 +08:00
let { linkAmount , isShowPasswordInput , password , newPassword , isExpireChecked , expType , expireDays , expDate } = this . state ;
2023-04-04 16:12:53 +08:00
2024-07-19 18:12:35 +08:00
if ( type === 'batch' ) {
2023-04-04 16:12:53 +08:00
if ( ! Number . isInteger ( parseInt ( linkAmount ) ) || parseInt ( linkAmount ) <= 1 ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'Please enter an integer bigger than 1 as number of links.' ) } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
2023-08-22 17:35:16 +08:00
if ( parseInt ( linkAmount ) > SHARE _LINK _MAX _NUMBER ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'Please enter an integer not bigger than {max_number} as number of links.' ) . replace ( '{max_number}' , SHARE _LINK _MAX _NUMBER ) } ) ;
2023-08-22 16:44:06 +08:00
return false ;
}
2023-04-04 16:12:53 +08:00
}
2024-07-19 18:12:35 +08:00
if ( type === 'single' && isShowPasswordInput ) {
2023-04-04 16:12:53 +08:00
if ( password . length === 0 ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'Please enter a password.' ) } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
if ( password . length < shareLinkPasswordMinLength ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'The password is too short.' ) } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
2024-11-20 20:37:46 +08:00
if ( password !== newPassword ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'Passwords don\'t match' ) } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
if ( Utils . getStrengthLevel ( password ) < shareLinkPasswordStrengthLevel ) {
2024-07-18 11:58:42 +08:00
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 ) } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
}
if ( isExpireChecked ) {
2024-07-19 18:12:35 +08:00
if ( expType === 'by-date' ) {
2023-04-04 16:12:53 +08:00
if ( ! expDate ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'Please select an expiration time' ) } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
return true ;
}
// by days
let reg = /^\d+$/ ;
if ( ! expireDays ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'Please enter days' ) } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
if ( ! reg . test ( expireDays ) ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : gettext ( 'Please enter a non-negative integer' ) } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
expireDays = parseInt ( expireDays ) ;
let minDays = shareLinkExpireDaysMin ;
let maxDays = shareLinkExpireDaysMax ;
2024-07-19 18:12:35 +08:00
if ( minDays !== 0 && maxDays === 0 ) {
2023-04-04 16:12:53 +08:00
if ( expireDays < minDays ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : 'Please enter valid days' } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
}
2024-07-23 23:08:53 +08:00
if ( minDays === 0 && maxDays !== 0 ) {
2023-04-04 16:12:53 +08:00
if ( expireDays > maxDays ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : 'Please enter valid days' } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
}
if ( minDays !== 0 && maxDays !== 0 ) {
if ( expireDays < minDays || expireDays > maxDays ) {
2024-07-18 11:58:42 +08:00
this . setState ( { errorInfo : 'Please enter valid days' } ) ;
2023-04-04 16:12:53 +08:00
return false ;
}
}
2024-07-18 11:58:42 +08:00
this . setState ( { expireDays : expireDays } ) ;
2023-04-04 16:12:53 +08:00
}
return true ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
onLinkAmountChange = ( e ) => {
this . setState ( {
linkAmount : e . target . value
} ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
goBack = ( ) => {
this . props . setMode ( '' ) ;
2023-09-13 08:40:50 +08:00
} ;
2023-04-04 16:12:53 +08:00
2024-07-19 18:12:35 +08:00
setScope = ( e ) => {
this . setState ( { currentScope : e . target . value , selectedOption : null , inputEmails : '' } ) ;
} ;
handleSelectChange = ( option ) => {
this . setState ( { selectedOption : option } ) ;
} ;
handleInputChange = ( e ) => {
this . setState ( {
inputEmails : e . target . value
} ) ;
} ;
2023-04-04 16:12:53 +08:00
render ( ) {
2024-07-18 11:58:42 +08:00
const { userPerm , type , permissionOptions } = this . props ;
2023-04-04 16:12:53 +08:00
const { isCustomPermission } = Utils . getUserPermission ( userPerm ) ;
return (
< Fragment >
< div className = "d-flex align-items-center pb-2 border-bottom" >
< h6 className = "font-weight-normal m-0" >
2024-06-28 08:39:44 +08:00
< button className = "sf3-font sf3-font-arrow rotate-180 d-inline-block back-icon border-0 bg-transparent text-secondary p-0 mr-2" onClick = { this . goBack } title = { gettext ( 'Back' ) } aria - label = { gettext ( 'Back' ) } > < / b u t t o n >
2024-07-19 18:12:35 +08:00
{ type === 'batch' ? gettext ( 'Generate links in batch' ) : gettext ( 'Generate Link' ) }
2024-07-18 11:58:42 +08:00
< / h 6 >
2023-04-04 16:12:53 +08:00
< / d i v >
2023-04-10 15:57:39 +08:00
< Form className = "pt-4" >
2024-07-19 18:12:35 +08:00
{ type === 'batch' && (
2023-04-04 16:12:53 +08:00
< FormGroup >
2023-04-10 15:57:39 +08:00
< Label for = "link-number" className = "p-0" > { gettext ( 'Number of links' ) } < / L a b e l >
2024-07-18 11:58:42 +08:00
< Input type = "number" id = "link-number" value = { this . state . linkAmount } onChange = { this . onLinkAmountChange } style = { { width : inputWidth } } / >
2023-04-04 16:12:53 +08:00
< / F o r m G r o u p >
) }
< FormGroup check >
{ shareLinkForceUsePassword ? (
< Label check >
< Input type = "checkbox" checked readOnly disabled / >
< span > { gettext ( 'Add password protection' ) } < / s p a n >
< / L a b e l >
) : (
< Label check >
< Input type = "checkbox" checked = { this . state . isShowPasswordInput } onChange = { this . onPasswordInputChecked } / >
< span > { gettext ( 'Add password protection' ) } < / s p a n >
< / L a b e l >
) }
2024-07-19 18:12:35 +08:00
{ type !== 'batch' && this . state . isShowPasswordInput &&
2023-04-04 16:12:53 +08:00
< div className = "ml-4" >
< FormGroup >
< Label for = "passwd" > { gettext ( 'Password' ) } < / L a b e l >
< span className = "tip" > { gettext ( '(at least {passwordMinLength} characters and includes {passwordStrengthLevel} of the following: number, upper letter, lower letter and other symbols)' ) . replace ( '{passwordMinLength}' , shareLinkPasswordMinLength ) . replace ( '{passwordStrengthLevel}' , shareLinkPasswordStrengthLevel ) } < / s p a n >
2024-07-18 11:58:42 +08:00
< InputGroup style = { { width : inputWidth } } >
2023-04-04 16:12:53 +08:00
< Input id = "passwd" type = { this . state . isPasswordVisible ? 'text' : 'password' } value = { this . state . password || '' } onChange = { this . inputPassword } / >
< InputGroupAddon addonType = "append" >
2024-06-28 08:39:44 +08:00
< Button onClick = { this . togglePasswordVisible } >
2024-07-18 11:58:42 +08:00
< i className = { ` link-operation-icon sf3-font sf3-font-eye ${ this . state . isPasswordVisible ? '' : '-slash' } ` } > < / i >
2024-06-28 08:39:44 +08:00
< / B u t t o n >
< Button onClick = { this . generatePassword } >
< i className = "link-operation-icon sf3-font sf3-font-magic" > < / i >
< / B u t t o n >
2023-04-04 16:12:53 +08:00
< / I n p u t G r o u p A d d o n >
< / I n p u t G r o u p >
< / F o r m G r o u p >
< FormGroup >
< Label for = "passwd-again" > { gettext ( 'Password again' ) } < / L a b e l >
2024-11-20 20:37:46 +08:00
< Input id = "passwd-again" style = { { width : inputWidth } } type = { this . state . isPasswordVisible ? 'text' : 'password' } value = { this . state . newPassword || '' } onChange = { this . inputPasswordNew } / >
2023-04-04 16:12:53 +08:00
< / F o r m G r o u p >
< / d i v >
}
< / F o r m G r o u p >
< FormGroup check >
< Label check >
{ this . isExpireDaysNoLimit ? (
< Input type = "checkbox" onChange = { this . onExpireChecked } / >
) : (
< Input type = "checkbox" checked readOnly disabled / >
) }
< span > { gettext ( 'Add auto expiration' ) } < / s p a n >
< / L a b e l >
{ this . state . isExpireChecked &&
< div className = "ml-4" >
< SetLinkExpiration
minDays = { shareLinkExpireDaysMin }
maxDays = { shareLinkExpireDaysMax }
defaultDays = { shareLinkExpireDaysDefault }
expType = { this . state . expType }
setExpType = { this . setExpType }
expireDays = { this . state . expireDays }
onExpireDaysChanged = { this . onExpireDaysChanged }
expDate = { this . state . expDate }
onExpDateChanged = { this . onExpDateChanged }
/ >
< / d i v >
}
< / F o r m G r o u p >
2024-07-11 14:28:00 +08:00
{ ! isCustomPermission && (
2023-04-04 16:12:53 +08:00
< FormGroup check >
< Label check >
< span > { gettext ( 'Set permission' ) } < / s p a n >
< / L a b e l >
{ permissionOptions . map ( ( item , index ) => {
return (
< FormGroup check className = "ml-4" key = { index } >
< Label check >
< Input type = "radio" name = "permission" value = { item } checked = { this . state . currentPermission == item } onChange = { this . setPermission } className = "mr-1" / >
{ Utils . getShareLinkPermissionObject ( item ) . text }
< / L a b e l >
< / F o r m G r o u p >
) ;
} ) }
< / F o r m G r o u p >
) }
2024-07-19 18:12:35 +08:00
{ type !== 'batch' && (
< FormGroup check >
< Label check >
2024-08-16 16:28:19 +08:00
< span > { gettext ( 'Set access scope' ) } < / s p a n >
2024-07-19 18:12:35 +08:00
< / L a b e l >
< FormGroup check className = "ml-4" >
< Label check >
< Input type = "radio" name = 'scope' value = { 'all_users' } checked = { this . state . currentScope === 'all_users' } onChange = { this . setScope } className = "mr-1" / >
{ gettext ( 'Anyone with the link' ) }
< / L a b e l >
< / F o r m G r o u p >
< FormGroup check className = "ml-4" >
< Label check >
< Input type = "radio" name = 'scope' value = { 'specific_users' } checked = { this . state . currentScope === 'specific_users' } onChange = { this . setScope } className = "mr-1" / >
{ gettext ( 'Specific users in the team' ) }
< / L a b e l >
{ this . state . currentScope === 'specific_users' &&
< UserSelect
ref = "userSelect"
isMulti = { true }
placeholder = { gettext ( 'Search users' ) }
onSelectChange = { this . handleSelectChange }
/ >
}
< / F o r m G r o u p >
{ isEmailConfigured && (
< FormGroup check className = "ml-4" >
< Label check >
< Input type = "radio" name = 'scope' value = { 'specific_emails' } checked = { this . state . currentScope === 'specific_emails' } onChange = { this . setScope } className = "mr-1" / >
{ gettext ( 'Specific people with email address' ) }
< / L a b e l >
{ this . state . currentScope === 'specific_emails' &&
< input type = "text" className = "form-control" value = { this . state . inputEmails } onChange = { this . handleInputChange } placeholder = { gettext ( 'Emails, separated by \',\'' ) } / >
}
< / F o r m G r o u p >
) }
< / F o r m G r o u p >
) }
2023-04-04 16:12:53 +08:00
{ this . state . errorInfo && < Alert color = "danger" className = "mt-2" > { gettext ( this . state . errorInfo ) } < / A l e r t > }
2024-07-13 15:15:22 +08:00
< Button color = "primary" onClick = { this . generateShareLink } className = "mt-2 ml-1 mb-1" > { gettext ( 'Generate' ) } < / B u t t o n >
2023-04-04 16:12:53 +08:00
< / F o r m >
< / F r a g m e n t >
) ;
}
}
LinkCreation . propTypes = propTypes ;
export default LinkCreation ;