mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-07 01:41:39 +00:00
ADD: dtable-share-link-ui & GET DELETE apis about dtable-share-link (#4117)
* ADD: dtable-share-link-ui ADD: GET DELETE apis about dtable-share-link * ADD: dtable_from_link_view & dtable_file_link_view_react.html * MOD: some error_msgs and rename dtable_share_link_view and move dtable_share_link_view's url to dtable's urls * FIX: dtableShareLinks' is_expired
This commit is contained in:
378
frontend/src/components/dialog/generate-dtable-share-link.js
Normal file
378
frontend/src/components/dialog/generate-dtable-share-link.js
Normal file
@@ -0,0 +1,378 @@
|
||||
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 { gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, shareLinkPasswordMinLength, canSendShareLinkEmail } 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 DTableShareLink from '../../models/dtable-share-link';
|
||||
|
||||
const propTypes = {
|
||||
workspaceID: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
closeShareDialog: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class GenerateDTableShareLink extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.isExpireDaysNoLimit = (parseInt(shareLinkExpireDaysMin) === 0 && parseInt(shareLinkExpireDaysMax) === 0 && shareLinkExpireDaysDefault == 0);
|
||||
this.defaultExpireDays = this.isExpireDaysNoLimit ? '' : shareLinkExpireDaysDefault;
|
||||
|
||||
this.isExpireDaysNoLimit = true;
|
||||
this.defaultExpireDays = '';
|
||||
|
||||
this.permissionOptions = ['read-only', 'read-write'];
|
||||
|
||||
this.state = {
|
||||
isValidate: false,
|
||||
isShowPasswordInput: false,
|
||||
isPasswordVisible: false,
|
||||
isExpireChecked: !this.isExpireDaysNoLimit,
|
||||
password: '',
|
||||
passwdnew: '',
|
||||
expireDays: this.defaultExpireDays,
|
||||
errorInfo: '',
|
||||
sharedLinkInfo: null,
|
||||
isNoticeMessageShow: false,
|
||||
isLoading: true,
|
||||
currentPermission: this.permissionOptions[0],
|
||||
isSendLinkShown: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let workspaceID = this.props.workspaceID;
|
||||
let name = this.props.name;
|
||||
seafileAPI.getDTableShareLink(workspaceID, name).then((res) => {
|
||||
window.res = res;
|
||||
if (res.data.dtable_share_links.length !== 0) {
|
||||
let sharedLinkInfo = new ShareLink(res.data.dtable_share_links[0]);
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
sharedLinkInfo: sharedLinkInfo
|
||||
});
|
||||
} else {
|
||||
this.setState({isLoading: false});
|
||||
}
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
generateDTableShareLink = () => {
|
||||
let isValid = this.validateParamsInput();
|
||||
if (isValid) {
|
||||
this.setState({errorInfo: ''});
|
||||
let { workspaceID, name } = this.props;
|
||||
let { password, isExpireChecked, expireDays } = this.state;
|
||||
let permission = Utils.getDTableShareLinkPermissionObject(this.state.currentPermission).permission;
|
||||
const expireDaysSent = isExpireChecked ? expireDays : '';
|
||||
seafileAPI.createDTableShareLink(workspaceID, name, password, expireDaysSent, permission).then((res) => {
|
||||
let sharedLinkInfo = new DTableShareLink(res.data);
|
||||
this.setState({sharedLinkInfo: sharedLinkInfo});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onCopySharedLink = () => {
|
||||
let sharedLink = this.state.sharedLinkInfo.link;
|
||||
copy(sharedLink);
|
||||
toaster.success(gettext('Share link is copied to the clipboard.'));
|
||||
this.props.closeShareDialog();
|
||||
}
|
||||
|
||||
deleteShareLink = () => {
|
||||
let sharedLinkInfo = this.state.sharedLinkInfo;
|
||||
seafileAPI.deleteDTableShareLink(sharedLinkInfo.token).then(() => {
|
||||
this.setState({
|
||||
password: '',
|
||||
passwordnew: '',
|
||||
isShowPasswordInput: false,
|
||||
expireDays: this.defaultExpireDays,
|
||||
isExpireChecked: !this.isExpireDaysNoLimit,
|
||||
errorInfo: '',
|
||||
sharedLinkInfo: null,
|
||||
isNoticeMessageShow: false,
|
||||
});
|
||||
}).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});
|
||||
}
|
||||
|
||||
setPermission = (e) => {
|
||||
this.setState({currentPermission: e.target.value});
|
||||
}
|
||||
|
||||
validateParamsInput = () => {
|
||||
let { isShowPasswordInput , password, passwdnew, isExpireChecked, expireDays } = this.state;
|
||||
// validate password
|
||||
if (isShowPasswordInput) {
|
||||
if (password.length === 0) {
|
||||
this.setState({errorInfo: 'Please enter password'});
|
||||
return false;
|
||||
}
|
||||
if (password.length < shareLinkPasswordMinLength) {
|
||||
this.setState({errorInfo: 'Password is too short'});
|
||||
return false;
|
||||
}
|
||||
if (password !== passwdnew) {
|
||||
this.setState({errorInfo: 'Passwords don\'t match'});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// validate days
|
||||
// no limit
|
||||
let reg = /^\d+$/;
|
||||
if (this.isExpireDaysNoLimit) {
|
||||
if (isExpireChecked) {
|
||||
if (!expireDays) {
|
||||
this.setState({errorInfo: 'Please enter days'});
|
||||
return false;
|
||||
}
|
||||
if (!reg.test(expireDays)) {
|
||||
this.setState({errorInfo: 'Please enter a non-negative integer'});
|
||||
return false;
|
||||
}
|
||||
this.setState({expireDays: parseInt(expireDays)});
|
||||
}
|
||||
} else {
|
||||
if (!expireDays) {
|
||||
this.setState({errorInfo: 'Please enter days'});
|
||||
return false;
|
||||
}
|
||||
if (!reg.test(expireDays)) {
|
||||
this.setState({errorInfo: 'Please enter a non-negative integer'});
|
||||
return false;
|
||||
}
|
||||
|
||||
expireDays = parseInt(expireDays);
|
||||
let minDays = parseInt(shareLinkExpireDaysMin);
|
||||
let maxDays = parseInt(shareLinkExpireDaysMax);
|
||||
|
||||
if (minDays !== 0 && maxDays !== maxDays) {
|
||||
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;
|
||||
}
|
||||
|
||||
onNoticeMessageToggle = () => {
|
||||
this.setState({isNoticeMessageShow: !this.state.isNoticeMessageShow});
|
||||
}
|
||||
|
||||
toggleSendLink = () => {
|
||||
this.setState({ isSendLinkShown: !this.state.isSendLinkShown });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
if (this.state.isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
let passwordLengthTip = gettext('(at least {passwordLength} characters)');
|
||||
passwordLengthTip = passwordLengthTip.replace('{passwordLength}', shareLinkPasswordMinLength);
|
||||
|
||||
if (this.state.sharedLinkInfo) {
|
||||
let sharedLinkInfo = this.state.sharedLinkInfo;
|
||||
return (
|
||||
<div>
|
||||
<Form className="mb-4">
|
||||
<FormGroup className="mb-0">
|
||||
<dt className="text-secondary font-weight-normal">{gettext('Link:')}</dt>
|
||||
<dd className="d-flex">
|
||||
<span>{sharedLinkInfo.link}</span>{' '}
|
||||
{sharedLinkInfo.is_expired ?
|
||||
<span className="err-message">({gettext('Expired')})</span> :
|
||||
<span className="far fa-copy action-icon" onClick={this.onCopySharedLink}/>
|
||||
}
|
||||
</dd>
|
||||
</FormGroup>
|
||||
{sharedLinkInfo.expire_date && (
|
||||
<FormGroup className="mb-0">
|
||||
<dt className="text-secondary font-weight-normal">{gettext('Expiration Date:')}</dt>
|
||||
<dd>{moment(sharedLinkInfo.expire_date).format('YYYY-MM-DD hh:mm:ss')}</dd>
|
||||
</FormGroup>
|
||||
)}
|
||||
</Form>
|
||||
{(canSendShareLinkEmail && !this.state.isSendLinkShown && !this.state.isNoticeMessageShow) &&
|
||||
<Button onClick={this.toggleSendLink} className='mr-2'>{gettext('Send')}</Button>
|
||||
}
|
||||
{(!this.state.isSendLinkShown && !this.state.isNoticeMessageShow) &&
|
||||
<Button onClick={this.onNoticeMessageToggle}>{gettext('Delete')}</Button>
|
||||
}
|
||||
{this.state.isNoticeMessageShow &&
|
||||
<div className="alert alert-warning">
|
||||
<h4 className="alert-heading">{gettext('Are you sure you want to delete the share link?')}</h4>
|
||||
<p className="mb-4">{gettext('If the share link is deleted, no one will be able to access it any more.')}</p>
|
||||
<button className="btn btn-primary" onClick={this.deleteShareLink}>{gettext('Delete')}</button>{' '}
|
||||
<button className="btn btn-secondary" onClick={this.onNoticeMessageToggle}>{gettext('Cancel')}</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Form className="generate-share-link">
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<Input type="checkbox" onChange={this.onPasswordInputChecked}/>{' '}{gettext('Add password protection')}
|
||||
</Label>
|
||||
</FormGroup>
|
||||
{this.state.isShowPasswordInput &&
|
||||
<FormGroup className="link-operation-content" check>
|
||||
<Label className="font-weight-bold">{gettext('Password')}</Label>{' '}<span className="tip">{passwordLengthTip}</span>
|
||||
<InputGroup className="passwd">
|
||||
<Input type={this.state.isPasswordVisible ? 'text' : 'password'} value={this.state.password || ''} onChange={this.inputPassword}/>
|
||||
<InputGroupAddon addonType="append">
|
||||
<Button onClick={this.togglePasswordVisible}><i className={`link-operation-icon fas ${this.state.isPasswordVisible ? 'fa-eye': 'fa-eye-slash'}`}></i></Button>
|
||||
<Button onClick={this.generatePassword}><i className="link-operation-icon fas fa-magic"></i></Button>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<Label className="font-weight-bold">{gettext('Password again')}</Label>
|
||||
<Input className="passwd" type={this.state.isPasswordVisible ? 'text' : 'password'} value={this.state.passwdnew || ''} onChange={this.inputPasswordNew} />
|
||||
</FormGroup>
|
||||
}
|
||||
{this.isExpireDaysNoLimit && (
|
||||
<Fragment>
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<Input className="expire-checkbox" type="checkbox" onChange={this.onExpireChecked} />{' '}{gettext('Add auto expiration')}
|
||||
</Label>
|
||||
</FormGroup>
|
||||
{this.state.isExpireChecked &&
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<Input className="expire-input expire-input-border" type="text" value={this.state.expireDays} onChange={this.onExpireDaysChanged} readOnly={!this.state.isExpireChecked} /><span className="expir-span">{gettext('days')}</span>
|
||||
</Label>
|
||||
</FormGroup>
|
||||
}
|
||||
</Fragment>
|
||||
)}
|
||||
{!this.isExpireDaysNoLimit && (
|
||||
<Fragment>
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<Input className="expire-checkbox" type="checkbox" onChange={this.onExpireChecked} checked readOnly disabled/>{' '}{gettext('Add auto expiration')}
|
||||
</Label>
|
||||
</FormGroup>
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<Input className="expire-input expire-input-border" type="text" value={this.state.expireDays} onChange={this.onExpireDaysChanged} /><span className="expir-span">{gettext('days')}</span>
|
||||
{(parseInt(shareLinkExpireDaysMin) !== 0 && parseInt(shareLinkExpireDaysMax) !== 0) && (
|
||||
<span className="d-inline-block ml-7">({shareLinkExpireDaysMin} - {shareLinkExpireDaysMax}{' '}{gettext('days')})</span>
|
||||
)}
|
||||
{(parseInt(shareLinkExpireDaysMin) !== 0 && parseInt(shareLinkExpireDaysMax) === 0) && (
|
||||
<span className="d-inline-block ml-7">({gettext('Greater than or equal to')} {shareLinkExpireDaysMin}{' '}{gettext('days')})</span>
|
||||
)}
|
||||
{(parseInt(shareLinkExpireDaysMin) === 0 && parseInt(shareLinkExpireDaysMax) !== 0) && (
|
||||
<span className="d-inline-block ml-7">({gettext('Less than or equal to')} {shareLinkExpireDaysMax}{' '}{gettext('days')})</span>
|
||||
)}
|
||||
</Label>
|
||||
</FormGroup>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
<Fragment>
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<span>{gettext('Set permission')}</span>
|
||||
</Label>
|
||||
</FormGroup>
|
||||
{this.permissionOptions.map((item, index) => {
|
||||
return (
|
||||
<FormGroup check className="permission" key={index}>
|
||||
<Label className="form-check-label">
|
||||
<Input type="radio" name="permission" value={item} checked={this.state.currentPermission == item} onChange={this.setPermission} className="mr-1" />
|
||||
{Utils.getDTableShareLinkPermissionObject(item).text}
|
||||
</Label>
|
||||
</FormGroup>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
|
||||
{this.state.errorInfo && <Alert color="danger" className="mt-2">{gettext(this.state.errorInfo)}</Alert>}
|
||||
<Button onClick={this.generateDTableShareLink} className="mt-2">{gettext('Generate')}</Button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GenerateDTableShareLink.propTypes = propTypes;
|
||||
|
||||
export default GenerateDTableShareLink;
|
@@ -5,6 +5,7 @@ import {Modal, ModalHeader, ModalBody, Nav, NavItem, NavLink, TabContent, TabPan
|
||||
import ShareTableToUser from './share-table-to-user';
|
||||
|
||||
import '../../css/share-link-dialog.css';
|
||||
import GenerateDTableShareLink from './generate-dtable-share-link';
|
||||
|
||||
const propTypes = {
|
||||
currentTable: PropTypes.object.isRequired,
|
||||
@@ -40,6 +41,13 @@ class ShareTableDialog extends React.Component {
|
||||
>{gettext('Share to user')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className={activeTab === 'shareLink' ? 'active' : ''}
|
||||
onClick={this.toggle.bind(this, 'shareLink')}
|
||||
>{gettext('Share link')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
</Fragment>
|
||||
</Nav>
|
||||
</div>
|
||||
@@ -51,6 +59,13 @@ class ShareTableDialog extends React.Component {
|
||||
currentTable={this.props.currentTable}
|
||||
/>
|
||||
</TabPane>
|
||||
{activeTab === 'shareLink' &&
|
||||
<GenerateDTableShareLink
|
||||
workspaceID={this.props.currentTable.workspace_id}
|
||||
name={this.props.currentTable.name}
|
||||
closeShareDialog={this.props.ShareCancel}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
</TabContent>
|
||||
</div>
|
||||
|
16
frontend/src/models/dtable-share-link.js
Normal file
16
frontend/src/models/dtable-share-link.js
Normal file
@@ -0,0 +1,16 @@
|
||||
class DTableShareLink {
|
||||
|
||||
constructor(object) {
|
||||
this.workspaceID = object.workspace_id;
|
||||
this.permissions = object.permission;
|
||||
this.username = object.username;
|
||||
this.is_expired = object.is_expired;
|
||||
this.expire_date = object.expire_date;
|
||||
this.token = object.token;
|
||||
this.link = object.link;
|
||||
this.ctime = object.ctime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DTableShareLink;
|
@@ -575,6 +575,23 @@ export const Utils = {
|
||||
}
|
||||
},
|
||||
|
||||
getDTableShareLinkPermissionObject: function(permission) {
|
||||
switch (permission) {
|
||||
case 'read-only':
|
||||
return {
|
||||
value: permission,
|
||||
text: 'read-only',
|
||||
permission: 'r'
|
||||
};
|
||||
case 'read-write':
|
||||
return {
|
||||
value: permission,
|
||||
text: 'read-write',
|
||||
permission: 'rw'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
formatSize: function(options) {
|
||||
/*
|
||||
* param: {bytes, precision}
|
||||
|
@@ -14,6 +14,7 @@ from seahub.api2.endpoints.dtable import WRITE_PERMISSION_TUPLE
|
||||
from seahub.api2.permissions import CanGenerateShareLink
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.constants import PERMISSION_READ
|
||||
from seahub.dtable.models import DTables, DTableShareLinks, Workspaces
|
||||
from seahub.settings import SHARE_LINK_EXPIRE_DAYS_MAX, \
|
||||
SHARE_LINK_EXPIRE_DAYS_MIN, SHARE_LINK_EXPIRE_DAYS_DEFAULT, SHARE_LINK_PASSWORD_MIN_LENGTH
|
||||
@@ -33,6 +34,8 @@ def get_share_dtable_link_info(sdl, dtable):
|
||||
'dtable': dtable.name,
|
||||
'dtable_id': dtable.id,
|
||||
'workspace_id': dtable.workspace_id,
|
||||
'expire_date': sdl.expire_date,
|
||||
'ctime': sdl.ctime,
|
||||
}
|
||||
|
||||
return data
|
||||
@@ -44,6 +47,43 @@ class DTableShareLinksView(APIView):
|
||||
permission_classes = (IsAuthenticated, CanGenerateShareLink)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
get dtable all share links of such user
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
username = request.user.username
|
||||
workspace_id = request.GET.get('workspace_id')
|
||||
if not workspace_id:
|
||||
error_msg = _('workspace_id invalid.')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
table_name = request.GET.get('table_name')
|
||||
if not table_name:
|
||||
error_msg = _('table_name invalid.')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
# resource check
|
||||
workspace = Workspaces.objects.get_workspace_by_id(workspace_id)
|
||||
if not workspace:
|
||||
error_msg = _('Workspace %(workspace)s not found' % {'workspace': workspace_id})
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
repo = seafile_api.get_repo(workspace.repo_id)
|
||||
if not repo:
|
||||
error_msg = _('Library %(workspace)s not found' % {'workspace': workspace_id})
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
dtable = DTables.objects.get_dtable(workspace_id, table_name)
|
||||
if not dtable:
|
||||
error_msg = _('DTable %(table)s not found' % {'table': table_name})
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
# get table's all links of user
|
||||
dsls = DTableShareLinks.objects.filter(dtable=dtable, username=username)
|
||||
results = [get_share_dtable_link_info(item, dtable) for item in dsls]
|
||||
return Response({
|
||||
'dtable_share_links': results
|
||||
})
|
||||
|
||||
def post(self, request):
|
||||
# argument check
|
||||
workspace_id = request.data.get('workspace_id')
|
||||
@@ -62,9 +102,11 @@ class DTableShareLinksView(APIView):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
try:
|
||||
expire_days = int(request.data.get('expire_days'))
|
||||
expire_days = int(request.data.get('expire_days', 0))
|
||||
except ValueError:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'expire_days invalid')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, _('expire_days invalid'))
|
||||
except TypeError:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, _('expire_days invalid'))
|
||||
|
||||
if expire_days <= 0:
|
||||
if SHARE_LINK_EXPIRE_DAYS_DEFAULT > 0:
|
||||
@@ -91,6 +133,7 @@ class DTableShareLinksView(APIView):
|
||||
if link_permission and link_permission not in [perm[0] for perm in DTableShareLinks.PERMISSION_CHOICES]:
|
||||
error_msg = _('Permission invalid')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
link_permission = link_permission if link_permission else PERMISSION_READ
|
||||
|
||||
# resource check
|
||||
workspace = Workspaces.objects.get_workspace_by_id(workspace_id)
|
||||
@@ -127,3 +170,28 @@ class DTableShareLinksView(APIView):
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
data = get_share_dtable_link_info(sdl, dtable)
|
||||
return Response(data)
|
||||
|
||||
|
||||
class DTableSharedLinkView(APIView):
|
||||
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated, CanGenerateShareLink)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
|
||||
def delete(self, request, token):
|
||||
dsl = DTableShareLinks.objects.filter(token=token).first()
|
||||
if not dsl:
|
||||
return Response({'success': True})
|
||||
|
||||
username = request.user.username
|
||||
if not dsl.is_owner(username):
|
||||
error_msg = _('Permission denied.')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
try:
|
||||
dsl.delete()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = _('Internal server error')
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
return Response({'success': True})
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import uuid
|
||||
import hmac
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.utils import timezone
|
||||
from hashlib import sha1
|
||||
import datetime
|
||||
|
||||
@@ -254,7 +255,7 @@ class DTableAPIToken(models.Model):
|
||||
class DTableShareLinksManager(models.Manager):
|
||||
|
||||
def create_link(self, dtable_id, username,
|
||||
password=None, expire_date=None, permission=None):
|
||||
password=None, expire_date=None, permission='r'):
|
||||
if password:
|
||||
password = make_password(password)
|
||||
token = gen_token(max_length=config.SHARE_LINK_TOKEN_LENGTH)
|
||||
@@ -287,6 +288,15 @@ class DTableShareLinks(models.Model):
|
||||
class Meta:
|
||||
db_table = 'dtable_share_links'
|
||||
|
||||
def is_owner(self, username):
|
||||
return self.username == username
|
||||
|
||||
def is_expired(self):
|
||||
if not self.expire_date:
|
||||
return False
|
||||
else:
|
||||
return self.expire_date < timezone.now()
|
||||
|
||||
|
||||
class DTableFormLinksManager(models.Manager):
|
||||
|
||||
|
@@ -1,12 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import dtable_file_view, dtable_asset_access, dtable_asset_file_view, dtable_form_view
|
||||
|
||||
from .views import dtable_file_view, dtable_asset_access, dtable_asset_file_view, dtable_form_view, \
|
||||
dtable_share_link_view
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^workspace/(?P<workspace_id>\d+)/dtable/(?P<name>.*)/$', dtable_file_view, name='dtable_file_view'),
|
||||
url(r'^workspace/(?P<workspace_id>\d+)/asset/(?P<dtable_id>[-0-9a-f]{36})/(?P<path>.*)$', dtable_asset_access, name='dtable_asset_access'),
|
||||
url(r'^workspace/(?P<workspace_id>\d+)/asset-file/(?P<dtable_id>[-0-9a-f]{36})/(?P<path>.*)$', dtable_asset_file_view, name='dtable_asset_file_view'),
|
||||
url(r'^dtable/forms/(?P<token>[-0-9a-f]{36})$', dtable_form_view, name='dtable_form_view')
|
||||
url(r'^dtable/forms/(?P<token>[-0-9a-f]{36})$', dtable_form_view, name='dtable_form_view'),
|
||||
url(r'^dtable/links/(?P<token>[-0-9a-f]+)/$', dtable_share_link_view, name='dtable_share_link_view'),
|
||||
]
|
||||
|
@@ -81,4 +81,4 @@ def gen_share_dtable_link(token):
|
||||
service_url = get_service_url()
|
||||
assert service_url is not None
|
||||
service_url = service_url.rstrip('/')
|
||||
return '%s/dtable/link/%s' % (service_url, token)
|
||||
return '%s/dtable/links/%s' % (service_url, token)
|
||||
|
@@ -10,7 +10,7 @@ from django.shortcuts import render
|
||||
from django.utils.translation import ugettext as _
|
||||
from seaserv import seafile_api
|
||||
|
||||
from seahub.dtable.models import Workspaces, DTables, DTableFormLinks
|
||||
from seahub.dtable.models import Workspaces, DTables, DTableFormLinks, DTableShareLinks
|
||||
from seahub.utils import normalize_file_path, render_error, render_permission_error, \
|
||||
gen_file_get_url, get_file_type_and_ext, gen_inner_file_get_url
|
||||
from seahub.auth.decorators import login_required
|
||||
@@ -236,3 +236,45 @@ def dtable_form_view(request, token):
|
||||
}
|
||||
|
||||
return render(request, 'dtable_form_view_react.html', return_dict)
|
||||
|
||||
|
||||
def dtable_share_link_view(request, token):
|
||||
dsl = DTableShareLinks.objects.filter(token=token).first()
|
||||
if not dsl:
|
||||
return render_error(request, _('Share link does not exist'))
|
||||
if dsl.is_expired():
|
||||
return render_error(request, _('Share link has expired'))
|
||||
|
||||
# resource check
|
||||
workspace_id = dsl.dtable.workspace.id
|
||||
workspace = Workspaces.objects.get_workspace_by_id(workspace_id)
|
||||
if not workspace:
|
||||
raise Http404
|
||||
|
||||
repo_id = workspace.repo_id
|
||||
repo = seafile_api.get_repo(repo_id)
|
||||
if not repo:
|
||||
raise Http404
|
||||
|
||||
name = dsl.dtable.name
|
||||
dtable = DTables.objects.get_dtable(workspace, name)
|
||||
if not dtable:
|
||||
return render_error(request, _('DTable does not exist'))
|
||||
|
||||
table_file_name = name + FILE_TYPE
|
||||
table_path = normalize_file_path(table_file_name)
|
||||
|
||||
return_dict = {
|
||||
'repo': repo,
|
||||
'filename': name,
|
||||
'path': table_path,
|
||||
'filetype': 'dtable',
|
||||
'workspace_id': workspace_id,
|
||||
'dtable_uuid': dtable.uuid.hex,
|
||||
'media_url': MEDIA_URL,
|
||||
'dtable_server': DTABLE_SERVER_URL,
|
||||
'dtable_socket': SEAFILE_COLLAB_SERVER,
|
||||
'permission': dsl.permission
|
||||
}
|
||||
|
||||
return render(request, 'dtable_share_link_view_react.html', return_dict)
|
||||
|
54
seahub/templates/dtable_share_link_view_react.html
Normal file
54
seahub/templates/dtable_share_link_view_react.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{% load seahub_tags i18n staticfiles %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ LANGUAGE_CODE }}">
|
||||
<head>
|
||||
<title>{{ filename }}</title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="keywords" content="{% trans "File Collaboration Team Organization" %}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<link rel="shortcut icon" href="{{ MEDIA_URL }}{% if filetype == 'dtable' %}{{ dtable_favicon_path }}{% else %}{{ favicon_path }}{% endif %}" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}fontawesome/css/fontawesome-all.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/seafile-ui.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/sf_font3/iconfont.css" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/dtable-font.css" />
|
||||
{% render_bundle 'viewDataGrid' 'css' %}
|
||||
{% if branding_css != '' %}<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}{{ branding_css }}" />{% endif %}
|
||||
{% if enable_branding_css %}<link rel="stylesheet" type="text/css" href="{% url 'custom_css' %}" />{% endif %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wrapper" class="{{ LANGUAGE_CODE }}"></div>
|
||||
<div id="modal-wrapper" class="{{ LANGUAGE_CODE }}"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.app = {
|
||||
config: {
|
||||
siteRoot: '{{ SITE_ROOT }}',
|
||||
},
|
||||
pageOptions: {
|
||||
server: '{{ service_url }}',
|
||||
csrfToken: "{{ csrf_token }}",
|
||||
name: "{{request.user.username|email2nickname|escapejs}}",
|
||||
username: "{{request.user.username|escapejs}}",
|
||||
|
||||
fileName: '{{ filename|escapejs }}',
|
||||
filePath: '{{ path|escapejs }}',
|
||||
workspaceID: '{{ workspace_id }}',
|
||||
dtableUuid: '{{ dtable_uuid }}',
|
||||
mediaUrl: '{{ media_url }}',
|
||||
dtableServer: '{{ dtable_server }}',
|
||||
dtableSocket: '{{ dtable_socket }}',
|
||||
lang: '{{ LANGUAGE_CODE }}',
|
||||
permission: '{{ permission }}',
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="{{ STATIC_URL }}scripts/i18n/{{ LANGUAGE_CODE }}/djangojs.js"></script>
|
||||
{% render_bundle 'commons' %}
|
||||
{% render_bundle 'viewDataGrid' 'js' %}
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -43,7 +43,10 @@
|
||||
mediaUrl: '{{ media_url }}',
|
||||
dtableServer: '{{ dtable_server }}',
|
||||
dtableSocket: '{{ dtable_socket }}',
|
||||
lang: '{{ LANGUAGE_CODE }}'
|
||||
lang: '{{ LANGUAGE_CODE }}',
|
||||
shareLinkExpireDaysMin: '{{ share_link_expire_days_min }}',
|
||||
shareLinkExpireDaysMax: '{{ share_link_expire_days_max }}',
|
||||
shareLinkExpireDaysDefault: '{{ share_link_expire_days_default }}',
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@@ -4,7 +4,6 @@ from django.conf.urls import url, include
|
||||
# from django.views.generic.simple import direct_to_template
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from seahub.api2.endpoints.dtable_share_links import DTableShareLinksView
|
||||
from seahub.views import *
|
||||
from seahub.views.sysadmin import *
|
||||
from seahub.views.ajax import *
|
||||
@@ -98,6 +97,7 @@ from seahub.api2.endpoints.dtable_forms import DTableFormLinksView, DTableFormLi
|
||||
from seahub.api2.endpoints.dtable_share import SharedDTablesView, DTableShareView
|
||||
from seahub.api2.endpoints.dtable_related_users import DTableRelatedUsersView
|
||||
from seahub.api2.endpoints.recent_added_files import RecentAddedFilesView
|
||||
from seahub.api2.endpoints.dtable_share_links import DTableShareLinksView, DTableSharedLinkView
|
||||
|
||||
|
||||
# Admin
|
||||
@@ -387,6 +387,7 @@ urlpatterns = [
|
||||
url(r'^api/v2.1/workspace/(?P<workspace_id>\d+)/dtable-asset-upload-link/$', DTableAssetUploadLinkView.as_view(), name='api-v2.1-workspace-dtable-asset-upload-link'),
|
||||
url(r'^api/v2.1/dtables/shared/$', SharedDTablesView.as_view(), name='api-v2.1-dtables-share'),
|
||||
url(r'^api/v2.1/dtables/share-links/$', DTableShareLinksView.as_view(), name='api-v2.1-dtables-share-links'),
|
||||
url(r'^api/v2.1/dtables/share-links/(?P<token>[0-9a-f]+)/$', DTableSharedLinkView.as_view(), name='api-v2.1-dtables-share-link'),
|
||||
url(r'^api/v2.1/workspace/(?P<workspace_id>\d+)/dtable/(?P<name>.*)/share/$', DTableShareView.as_view(), name='api-v2.1-dtable-share'),
|
||||
url(r'^api/v2.1/workspace/(?P<workspace_id>\d+)/dtable/(?P<name>.*)/related-users/$', DTableRelatedUsersView.as_view(), name='api-v2.1-dtable-related-users'),
|
||||
url(r'^api/v2.1/workspace/(?P<workspace_id>\d+)/dtable/(?P<name>.*)/access-token/$', DTableAccessTokenView.as_view(), name='api-v2.1-dtable-access-token'),
|
||||
|
@@ -1272,4 +1272,8 @@ def react_fake_view(request, **kwargs):
|
||||
|
||||
@login_required
|
||||
def dtable_fake_view(request, **kwargs):
|
||||
return render(request, 'react_dtable.html')
|
||||
return render(request, 'react_dtable.html', {
|
||||
'share_link_expire_days_default': settings.SHARE_LINK_EXPIRE_DAYS_DEFAULT,
|
||||
'share_link_expire_days_min': SHARE_LINK_EXPIRE_DAYS_MIN,
|
||||
'share_link_expire_days_max': SHARE_LINK_EXPIRE_DAYS_MAX,
|
||||
})
|
||||
|
Reference in New Issue
Block a user