diff --git a/frontend/src/components/dialog/generate-dtable-share-link.js b/frontend/src/components/dialog/generate-dtable-share-link.js new file mode 100644 index 0000000000..d5b27f5191 --- /dev/null +++ b/frontend/src/components/dialog/generate-dtable-share-link.js @@ -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 ; + } + + let passwordLengthTip = gettext('(at least {passwordLength} characters)'); + passwordLengthTip = passwordLengthTip.replace('{passwordLength}', shareLinkPasswordMinLength); + + if (this.state.sharedLinkInfo) { + let sharedLinkInfo = this.state.sharedLinkInfo; + return ( +
+
+ +
{gettext('Link:')}
+
+ {sharedLinkInfo.link}{' '} + {sharedLinkInfo.is_expired ? + ({gettext('Expired')}) : + + } +
+
+ {sharedLinkInfo.expire_date && ( + +
{gettext('Expiration Date:')}
+
{moment(sharedLinkInfo.expire_date).format('YYYY-MM-DD hh:mm:ss')}
+
+ )} +
+ {(canSendShareLinkEmail && !this.state.isSendLinkShown && !this.state.isNoticeMessageShow) && + + } + {(!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.')}

+ {' '} + +
+ } +
+ ); + } else { + return ( +
+ + + + {this.state.isShowPasswordInput && + + {' '}{passwordLengthTip} + + + + + + + + + + + } + {this.isExpireDaysNoLimit && ( + + + + + {this.state.isExpireChecked && + + + + } + + )} + {!this.isExpireDaysNoLimit && ( + + + + + + + + + )} + + + + + + {this.permissionOptions.map((item, index) => { + return ( + + + + ); + })} + + + {this.state.errorInfo && {gettext(this.state.errorInfo)}} + +
+ ); + } + } +} + +GenerateDTableShareLink.propTypes = propTypes; + +export default GenerateDTableShareLink; diff --git a/frontend/src/components/dialog/share-table-dialog.js b/frontend/src/components/dialog/share-table-dialog.js index aec8496c80..6b7d9e446a 100644 --- a/frontend/src/components/dialog/share-table-dialog.js +++ b/frontend/src/components/dialog/share-table-dialog.js @@ -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')} + + {gettext('Share link')} + + @@ -51,6 +59,13 @@ class ShareTableDialog extends React.Component { currentTable={this.props.currentTable} /> + {activeTab === 'shareLink' && + + } diff --git a/frontend/src/models/dtable-share-link.js b/frontend/src/models/dtable-share-link.js new file mode 100644 index 0000000000..a0367549ce --- /dev/null +++ b/frontend/src/models/dtable-share-link.js @@ -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; diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index 72098f0e9b..5c6553f56e 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -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} diff --git a/seahub/api2/endpoints/dtable_share_links.py b/seahub/api2/endpoints/dtable_share_links.py index c518926412..961034bfd4 100644 --- a/seahub/api2/endpoints/dtable_share_links.py +++ b/seahub/api2/endpoints/dtable_share_links.py @@ -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}) diff --git a/seahub/dtable/models.py b/seahub/dtable/models.py index f7cddd3f30..16a03635b8 100644 --- a/seahub/dtable/models.py +++ b/seahub/dtable/models.py @@ -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): diff --git a/seahub/dtable/urls.py b/seahub/dtable/urls.py index 3a9aea0002..24e508a7f3 100644 --- a/seahub/dtable/urls.py +++ b/seahub/dtable/urls.py @@ -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\d+)/dtable/(?P.*)/$', dtable_file_view, name='dtable_file_view'), url(r'^workspace/(?P\d+)/asset/(?P[-0-9a-f]{36})/(?P.*)$', dtable_asset_access, name='dtable_asset_access'), url(r'^workspace/(?P\d+)/asset-file/(?P[-0-9a-f]{36})/(?P.*)$', dtable_asset_file_view, name='dtable_asset_file_view'), - url(r'^dtable/forms/(?P[-0-9a-f]{36})$', dtable_form_view, name='dtable_form_view') + url(r'^dtable/forms/(?P[-0-9a-f]{36})$', dtable_form_view, name='dtable_form_view'), + url(r'^dtable/links/(?P[-0-9a-f]+)/$', dtable_share_link_view, name='dtable_share_link_view'), ] diff --git a/seahub/dtable/utils.py b/seahub/dtable/utils.py index f3bba999ca..f970c001e6 100644 --- a/seahub/dtable/utils.py +++ b/seahub/dtable/utils.py @@ -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) diff --git a/seahub/dtable/views.py b/seahub/dtable/views.py index 42b2d78d2b..4df8f9dbfa 100644 --- a/seahub/dtable/views.py +++ b/seahub/dtable/views.py @@ -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) diff --git a/seahub/templates/dtable_share_link_view_react.html b/seahub/templates/dtable_share_link_view_react.html new file mode 100644 index 0000000000..a440d2013f --- /dev/null +++ b/seahub/templates/dtable_share_link_view_react.html @@ -0,0 +1,54 @@ +{% load seahub_tags i18n staticfiles %} +{% load render_bundle from webpack_loader %} + + + + +{{ filename }} + + + + + + + + + +{% render_bundle 'viewDataGrid' 'css' %} +{% if branding_css != '' %}{% endif %} +{% if enable_branding_css %}{% endif %} + + + +
+ + + + +{% render_bundle 'commons' %} +{% render_bundle 'viewDataGrid' 'js' %} + + + diff --git a/seahub/templates/react_dtable.html b/seahub/templates/react_dtable.html index 677c146db2..7ae4cce4cf 100644 --- a/seahub/templates/react_dtable.html +++ b/seahub/templates/react_dtable.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 }}', } }; diff --git a/seahub/urls.py b/seahub/urls.py index 237a90eb81..d0067a7b2c 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -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\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[0-9a-f]+)/$', DTableSharedLinkView.as_view(), name='api-v2.1-dtables-share-link'), url(r'^api/v2.1/workspace/(?P\d+)/dtable/(?P.*)/share/$', DTableShareView.as_view(), name='api-v2.1-dtable-share'), url(r'^api/v2.1/workspace/(?P\d+)/dtable/(?P.*)/related-users/$', DTableRelatedUsersView.as_view(), name='api-v2.1-dtable-related-users'), url(r'^api/v2.1/workspace/(?P\d+)/dtable/(?P.*)/access-token/$', DTableAccessTokenView.as_view(), name='api-v2.1-dtable-access-token'), diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 25b347fc48..598265dce0 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -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, + })