1
0
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:
Alex Happy
2019-09-27 15:56:31 +08:00
committed by Daniel Pan
parent 702652d63e
commit c34969ce4b
13 changed files with 620 additions and 11 deletions

View 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;

View File

@@ -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>

View 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;

View File

@@ -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}

View File

@@ -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})

View File

@@ -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):

View File

@@ -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'),
]

View File

@@ -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)

View File

@@ -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)

View 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>

View File

@@ -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>

View File

@@ -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'),

View File

@@ -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,
})