1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-12 21:30:39 +00:00

Merge branch '7.0'

This commit is contained in:
plt
2019-08-12 17:28:08 +08:00
29 changed files with 358 additions and 72 deletions

View File

@@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import { Button, Modal, ModalHeader, Input, ModalBody, ModalFooter, Form, FormGroup, Label, Alert } from 'reactstrap';
import { gettext, enableEncryptedLibrary, repoPasswordMinLength } from '../../utils/constants';
import { gettext, enableEncryptedLibrary, repoPasswordMinLength, storages } from '../../utils/constants';
const propTypes = {
libraryType: PropTypes.string.isRequired,
@@ -20,6 +21,7 @@ class CreateRepoDialog extends React.Component {
password2: '',
errMessage: '',
permission: 'rw',
storage_id: storages.length ? storages[0].id : '',
isSubmitBtnActive: false,
};
this.newInput = React.createRef();
@@ -44,17 +46,14 @@ class CreateRepoDialog extends React.Component {
}
handleSubmit = () => {
let isValid= this.validateInputParams();
let isValid = this.validateInputParams();
if (isValid) {
let repoName = this.state.repoName.trim();
let password = this.state.encrypt ? this.state.password1 : '';
let permission= this.state.permission;
let repo = this.createRepo(repoName, password, permission);
let repoData = this.prepareRepoData();
if (this.props.libraryType === 'department') {
this.props.onCreateRepo(repo, 'department');
this.props.onCreateRepo(repoData, 'department');
return;
}
this.props.onCreateRepo(repo);
this.props.onCreateRepo(repoData);
}
}
@@ -118,6 +117,10 @@ class CreateRepoDialog extends React.Component {
this.setState({permission: permission});
}
handleStorageInputChange = (selectedItem) => {
this.setState({storage_id: selectedItem.value});
}
onEncrypted = (e) => {
let isChecked = e.target.checked;
this.setState({
@@ -126,25 +129,18 @@ class CreateRepoDialog extends React.Component {
});
}
createRepo = (repoName, password, permission) => {
prepareRepoData = () => {
let libraryType = this.props.libraryType;
let encrypt = password ? true : false;
let repoName = this.state.repoName.trim();
let password = this.state.encrypt ? this.state.password1 : '';
let permission = this.state.permission;
let repo = null;
if (libraryType === 'mine' || libraryType === 'public') {
repo = {
id: null,
name: repoName,
desc: '',
encrypted: encrypt,
passwd: password,
passwd1: password,
passwd2: password,
mtime: 0,
mtime_relative: '',
owner: '-',
owner_nickname: '-',
permission: 'rw',
storage_name: '-',
passwd: password
};
}
if (libraryType === 'group') {
@@ -160,6 +156,12 @@ class CreateRepoDialog extends React.Component {
passwd: password,
};
}
const storage_id = this.state.storage_id;
if (storage_id) {
repo.storage_id = storage_id;
}
return repo;
}
@@ -179,6 +181,17 @@ class CreateRepoDialog extends React.Component {
onChange={this.handleRepoNameChange}
/>
</FormGroup>
{storages.length > 0 && (
<FormGroup>
<Label for="storage-backend">{gettext('Storage Backend')}</Label>
<Select
id="storage-backend"
defaultValue={{value: storages[0].id, label: storages[0].name}}
options={storages.map((item, index) => { return {value: item.id, label: item.name}; })}
onChange={this.handleStorageInputChange}
/>
</FormGroup>
)}
{this.props.libraryType === 'group' && (
<FormGroup>
<Label for="exampleSelect">{gettext('Permission')}</Label>
@@ -191,7 +204,7 @@ class CreateRepoDialog extends React.Component {
{enableEncryptedLibrary &&
<div>
<FormGroup check>
<Input type="checkbox" id="encrypt" onChange={this.onEncrypted}/>
<Input type="checkbox" id="encrypt" onChange={this.onEncrypted} />
<Label for="encrypt">{gettext('Encrypt')}</Label>
</FormGroup>
{!this.state.disabled &&

View File

@@ -6,6 +6,7 @@ import copy from '@seafile/seafile-editor/dist/utils/copy-to-clipboard';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import Loading from '../loading';
const propTypes = {
path: PropTypes.string.isRequired,
@@ -18,6 +19,7 @@ class InternalLink extends React.Component {
super(props);
this.state = {
smartLink: '',
isInternalLoding: true,
};
}
@@ -25,7 +27,8 @@ class InternalLink extends React.Component {
let { repoID, path, direntType } = this.props;
seafileAPI.getInternalLink(repoID, path, direntType).then(res => {
this.setState({
smartLink: res.data.smart_link
smartLink: res.data.smart_link,
isInternalLoding: false
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
@@ -42,6 +45,9 @@ class InternalLink extends React.Component {
}
render() {
if (this.state.isInternalLoding) {
return(<Loading />);
}
return (
<div>
<p className="tip mb-1">

View File

@@ -34,7 +34,7 @@ class AddOrgAdminDialog extends React.Component {
addOrgAdmin = () => {
if (!this.state.selectedOption) return;
const userEmail = this.state.selectedOption.email;
seafileAPI.setOrgAdmin(orgID, userEmail, true).then(res => {
seafileAPI.orgAdminSetOrgAdmin(orgID, userEmail, true).then(res => {
let userInfo = new OrgUserInfo(res.data);
this.props.onAddedOrgAdmin(userInfo);
}).catch(error => {

View File

@@ -37,7 +37,7 @@ class SetOrgUserContactEmail extends React.Component {
submitBtnDisabled: true
});
seafileAPI.setOrgUserContactEmail(orgID, email, contactEmail).then((res) => {
seafileAPI.orgAdminSetOrgUserContactEmail(orgID, email, contactEmail).then((res) => {
const newContactEmail = contactEmail ? res.data.contact_email : '';
this.props.updateContactEmail(newContactEmail);
this.props.toggleDialog();

View File

@@ -39,7 +39,7 @@ class SetOrgUserName extends React.Component {
// when name is '', api returns the previous name
// but newName needs to be ''
seafileAPI.setOrgUserName(orgID, email, name).then((res) => {
seafileAPI.orgAdminSetOrgUserName(orgID, email, name).then((res) => {
const newName = name ? res.data.name : '';
this.props.updateName(newName);
this.props.toggleDialog();

View File

@@ -46,7 +46,7 @@ class SetOrgUserQuota extends React.Component {
submitBtnDisabled: true
});
seafileAPI.setOrgUserQuota(orgID, email, quota).then((res) => {
seafileAPI.orgAdminSetOrgUserQuota(orgID, email, quota).then((res) => {
this.props.updateQuota(res.data.quota_total);
this.props.toggleDialog();
}).catch((error) => {

View File

@@ -98,11 +98,13 @@ class ShareDialog extends React.Component {
</NavLink>
</NavItem>
}
{itemType === 'dir' &&
<NavItem>
<NavLink className={activeTab === 'internalLink' ? 'active' : ''} onClick={this.toggle.bind(this, 'internalLink')}>
{gettext('Internal Link')}
</NavLink>
</NavItem>
}
{enableDirPrivateShare &&
<Fragment>
<NavItem>
@@ -139,13 +141,13 @@ class ShareDialog extends React.Component {
/>
</TabPane>
}
<TabPane tabId="internalLink">
{itemType === 'dir' && activeTab === 'internalLink' &&
<InternalLink
path={this.props.itemPath}
repoID={this.props.repoID}
direntType={itemType}
/>
</TabPane>
}
{enableDirPrivateShare &&
<Fragment>
<TabPane tabId="shareToUser">
@@ -190,12 +192,12 @@ class ShareDialog extends React.Component {
closeShareDialog={this.props.toggleDialog}
/>
</TabPane>
<TabPane tabId="internalLink">
{activeTab === 'internalLink' &&
<InternalLink
repoID={this.props.repoID}
path={this.props.itemPath}
repoID={this.props.repoID}
/>
</TabPane>
}
</TabContent>
</div>
</Fragment>

View File

@@ -403,6 +403,9 @@ class DirentListItem extends React.Component {
if (Utils.isIEBrower()) {
return false;
}
if (e.dataTransfer.dropEffect === 'copy') {
return;
}
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}

View File

@@ -580,6 +580,9 @@ class DirentListView extends React.Component {
if (Utils.isIEBrower()) {
return false;
}
if (e.dataTransfer.dropEffect === 'copy') {
return;
}
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}

View File

@@ -18,6 +18,7 @@ class Repo {
this.type = object.type;
this.starred = object.starred;
this.status = object.status;
this.storage_name = object.storage_name;
if (object.is_admin != undefined) {
this.is_admin = object.is_admin;
}

View File

@@ -13,10 +13,6 @@ import EmptyTip from '../../components/empty-tip';
import '../../css/invitations.css';
if (!canInvitePeople) {
location.href = siteRoot;
}
class Item extends React.Component {
constructor(props) {

View File

@@ -80,18 +80,18 @@ class MyLibraries extends Component {
}
onCreateRepo = (repo) => {
let permission = repo.permission;
seafileAPI.createMineRepo(repo).then((res) => {
let repo = {
const newRepo = new Repo({
repo_id: res.data.repo_id,
repo_name: res.data.repo_name,
size: res.data.repo_size,
mtime: res.data.mtime,
owner_email: res.data.email,
encrypted: res.data.encrypted,
permission: permission,
};
this.state.repoList.unshift(repo);
permission: res.data.permission,
storage_name: res.data.storage_name
});
this.state.repoList.unshift(newRepo);
this.setState({repoList: this.state.repoList});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);

View File

@@ -85,10 +85,10 @@ class MylibRepoListView extends React.Component {
<tr>
<th width="4%"></th>
<th width="4%"><span className="sr-only">{gettext('Library Type')}</span></th>
<th width="38%"><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {this.props.sortBy === 'name' && sortIcon}</a></th>
<th width={showStorageBackend ? '33%' : '38%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {this.props.sortBy === 'name' && sortIcon}</a></th>
<th width="14%"><span className="sr-only">{gettext('Actions')}</span></th>
<th width={showStorageBackend ? '15%' : '20%'}><a className="d-block table-sort-op" href="#" onClick={this.sortBySize}>{gettext('Size')} {this.props.sortBy === 'size' && sortIcon}</a></th>
{showStorageBackend ? <th width="10%">{gettext('Storage backend')}</th> : null}
{showStorageBackend ? <th width="15%">{gettext('Storage Backend')}</th> : null}
<th width={showStorageBackend ? '15%' : '20%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {this.props.sortBy === 'time' && sortIcon}</a></th>
</tr>
</thead>

View File

@@ -147,7 +147,7 @@ class Item extends Component {
deleteRepo = () => {
const repo = this.props.data;
seafileAPI.deleteOrgRepo(orgID, repo.repo_id).then((res) => {
seafileAPI.orgAdminDeleteOrgRepo(orgID, repo.repo_id).then((res) => {
this.setState({
deleted: true
});

View File

@@ -26,7 +26,7 @@ class OrgGroups extends Component {
}
initData = (page) => {
seafileAPI.listOrgGroups(orgID, page).then(res => {
seafileAPI.orgAdminListOrgGroups(orgID, page).then(res => {
let orgGroups = res.data.groups.map(item => {
return new OrgGroupInfo(item);
});
@@ -64,7 +64,7 @@ class OrgGroups extends Component {
}
deleteGroupItem = (group) => {
seafileAPI.deleteOrgGroup(orgID, group.id).then(res => {
seafileAPI.orgAdminDeleteOrgGroup(orgID, group.id).then(res => {
this.setState({
orgGroups: this.state.orgGroups.filter(item => item.id != group.id)
});

View File

@@ -28,7 +28,7 @@ class OrgLibraries extends Component {
}
initData = (page) => {
seafileAPI.listOrgRepos(orgID, page).then(res => {
seafileAPI.orgAdminListOrgRepos(orgID, page).then(res => {
let orgRepos = res.data.repo_list.map(item => {
return new OrgAdminRepo(item);
});
@@ -66,7 +66,7 @@ class OrgLibraries extends Component {
}
deleteRepoItem = (repo) => {
seafileAPI.deleteOrgRepo(orgID, repo.repoID).then(res => {
seafileAPI.orgAdminDeleteOrgRepo(orgID, repo.repoID).then(res => {
this.setState({
orgRepos: this.state.orgRepos.filter(item => item.repoID != repo.repoID)
});
@@ -239,7 +239,7 @@ class RepoItem extends React.Component {
onTransferRepo = (user) => {
let repo = this.props.repo;
seafileAPI.transferOrgRepo(orgID, repo.repoID, user.email).then(res => {
seafileAPI.orgAdminTransferOrgRepo(orgID, repo.repoID, user.email).then(res => {
this.props.transferRepoItem(repo.repoID, user);
let msg = gettext('Successfully transferred the library.');
toaster.success(msg);

View File

@@ -57,7 +57,7 @@ class UserItem extends React.Component {
toggleResetPW = () => {
const email = this.props.user.email;
toaster.success(gettext('Resetting user\'s password, please wait for a moment.'));
seafileAPI.resetOrgUserPassword(orgID, email).then(res => {
seafileAPI.orgAdminResetOrgUserPassword(orgID, email).then(res => {
let msg;
msg = gettext('Successfully reset password to %(passwd)s for user %(user)s.');
msg = msg.replace('%(passwd)s', res.data.new_password);
@@ -84,7 +84,7 @@ class UserItem extends React.Component {
statusCode = 0;
}
seafileAPI.changeOrgUserStatus(this.props.user.id, statusCode).then(res => {
seafileAPI.orgAdminChangeOrgUserStatus(this.props.user.id, statusCode).then(res => {
this.setState({
currentStatus: statusCode == 1 ? 'active' : 'inactive',
highlight: false,

View File

@@ -24,7 +24,7 @@ class OrgUserProfile extends Component {
}
componentDidMount() {
seafileAPI.getOrgUserInfo(orgID, this.props.email).then((res) => {
seafileAPI.orgAdminGetOrgUserInfo(orgID, this.props.email).then((res) => {
this.setState(Object.assign({
loading: false
}, res.data));

View File

@@ -24,7 +24,7 @@ class OrgUserOwnedRepos extends Component {
}
componentDidMount() {
seafileAPI.getOrgUserOwnedRepos(orgID, this.props.email).then((res) => {
seafileAPI.orgAdminGetOrgUserOwnedRepos(orgID, this.props.email).then((res) => {
this.setState(Object.assign({
loading: false
}, res.data));
@@ -147,7 +147,7 @@ class Item extends Component {
deleteRepo = () => {
const repo = this.props.data;
seafileAPI.deleteOrgRepo(orgID, repo.repo_id).then((res) => {
seafileAPI.orgAdminDeleteOrgRepo(orgID, repo.repo_id).then((res) => {
this.setState({
deleted: true
});

View File

@@ -22,7 +22,7 @@ class OrgUserSharedRepos extends Component {
}
componentDidMount() {
seafileAPI.getOrgUserBesharedRepos(orgID, this.props.email).then((res) => {
seafileAPI.orgAdminGetOrgUserBesharedRepos(orgID, this.props.email).then((res) => {
this.setState(Object.assign({
loading: false
}, res.data));

View File

@@ -45,7 +45,7 @@ class OrgUsers extends Component {
}
initOrgUsersData = (page) => {
seafileAPI.listOrgUsers(orgID, '', page).then(res => {
seafileAPI.orgAdminListOrgUsers(orgID, '', page).then(res => {
let userList = res.data.user_list.map(item => {
return new OrgUserInfo(item);
});
@@ -61,7 +61,7 @@ class OrgUsers extends Component {
}
addOrgUser = (email, name, password) => {
seafileAPI.addOrgUser(orgID, email, name, password).then(res => {
seafileAPI.orgAdminAddOrgUser(orgID, email, name, password).then(res => {
let userInfo = new OrgUserInfo(res.data);
this.state.orgUsers.unshift(userInfo);
this.setState({
@@ -79,7 +79,7 @@ class OrgUsers extends Component {
}
toggleOrgUsersDelete = (email) => {
seafileAPI.deleteOrgUser(orgID, email).then(res => {
seafileAPI.orgAdminDeleteOrgUser(orgID, email).then(res => {
let users = this.state.orgUsers.filter(item => item.email != email);
this.setState({orgUsers: users});
let msg = gettext('Successfully deleted %s');
@@ -92,7 +92,7 @@ class OrgUsers extends Component {
}
initOrgAdmin = () => {
seafileAPI.listOrgUsers(orgID, true).then(res => {
seafileAPI.orgAdminListOrgUsers(orgID, true).then(res => {
let userList = res.data.user_list.map(item => {
return new OrgUserInfo(item);
});
@@ -104,7 +104,7 @@ class OrgUsers extends Component {
}
toggleOrgAdminDelete = (email) => {
seafileAPI.deleteOrgUser(orgID, email).then(res => {
seafileAPI.orgAdminDeleteOrgUser(orgID, email).then(res => {
this.setState({
orgAdminUsers: this.state.orgAdminUsers.filter(item => item.email != email)
});
@@ -118,7 +118,7 @@ class OrgUsers extends Component {
}
toggleRevokeAdmin = (email) => {
seafileAPI.setOrgAdmin(orgID, email, false).then(res => {
seafileAPI.orgAdminSetOrgAdmin(orgID, email, false).then(res => {
this.setState({
orgAdminUsers: this.state.orgAdminUsers.filter(item => item.email != email)
});

View File

@@ -0,0 +1,98 @@
# Copyright (c) 2012-2016 Seafile Ltd.
# encoding: utf-8
import logging
import posixpath
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
from seaserv import seafile_api
from seahub.utils import is_pro_version, generate_file_audit_event_type
from seahub.utils.ms_excel import write_xls
from seahub.utils.timeutils import utc_to_local
from seahub.api2.endpoints.utils import check_time_period_valid, \
get_log_events_by_type_and_time
# Get an instance of a logger
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Export user storage to '../file-access-logs.xlsx'."
def add_arguments(self, parser):
# Named (optional) arguments
parser.add_argument(
'--path',
help='Folder path to save the file. If not passed, Seafile will save the file in current folder.',
)
parser.add_argument(
'--start-date',
help='For example, 2019-06-01.',
)
parser.add_argument(
'--end-date',
help='For example, 2019-07-01.',
)
def handle(self, *args, **options):
""" Export file access logs to excel.
"""
if not is_pro_version():
self.stdout.write("Failed to export excel, this feature is only in professional version.")
return
path = options['path']
start = str(options['start_date'])
end = str(options['end_date'])
if not check_time_period_valid(start, end):
self.stdout.write("Failed to export excel, invalid start or end date.")
return
events = get_log_events_by_type_and_time('file_audit', start, end)
head = [_("User"), _("Type"), _("IP"), _("Device"), _("Date"),
_("Library Name"), _("Library ID"), _("Library Owner"), _("File Path"),]
data_list = []
repo_obj_dict = {}
repo_owner_dict = {}
events.sort(lambda x, y: cmp(y.timestamp, x.timestamp))
for ev in events:
event_type, ev.show_device = generate_file_audit_event_type(ev)
repo_id = ev.repo_id
if repo_id not in repo_obj_dict:
repo = seafile_api.get_repo(repo_id)
repo_obj_dict[repo_id] = repo
else:
repo = repo_obj_dict[repo_id]
if repo:
repo_name = repo.name
if repo_id not in repo_owner_dict:
repo_owner = seafile_api.get_repo_owner(repo_id) or \
seafile_api.get_org_repo_owner(repo_id)
repo_owner_dict[repo_id] = repo_owner
else:
repo_owner = repo_owner_dict[repo_id]
else:
repo_name = _('Deleted')
repo_owner = '--'
username = ev.user if ev.user else _('Anonymous User')
date = utc_to_local(ev.timestamp).strftime('%Y-%m-%d %H:%M:%S') if \
ev.timestamp else ''
row = [username, event_type, ev.ip, ev.show_device,
date, repo_name, ev.repo_id, repo_owner, ev.file_path]
data_list.append(row)
excel_name = 'file-access-logs.xlsx'
wb = write_xls(_('file-access-logs'), head, data_list)
wb.save(posixpath.join(path, excel_name)) if path else wb.save(excel_name)

View File

@@ -0,0 +1,62 @@
# Copyright (c) 2012-2016 Seafile Ltd.
# encoding: utf-8
import logging
import posixpath
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
from seaserv import ccnet_api
from seahub.base.templatetags.seahub_tags import email2nickname, \
email2contact_email
from seahub.views.sysadmin import _populate_user_quota_usage
from seahub.utils.ms_excel import write_xls
from seahub.utils.file_size import byte_to_mb
# Get an instance of a logger
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Export user storage to '../User-Storage.xlsx'."
def add_arguments(self, parser):
# Named (optional) arguments
parser.add_argument(
'--path',
help='Folder path to save the file. If not passed, Seafile will save the file in current folder.',
)
def handle(self, *args, **options):
path = options['path']
db_users = ccnet_api.get_emailusers('DB', -1, -1)
ldap_import_users = ccnet_api.get_emailusers('LDAPImport', -1, -1)
all_users = db_users + ldap_import_users
head = [_("Email"), _("Name"), _("Contact Email"),
_("Space Usage") + "(MB)", _("Space Quota") + "(MB)"]
data_list = []
for user in all_users:
user_email = user.email
user_name = email2nickname(user_email)
user_contact_email = email2contact_email(user_email)
_populate_user_quota_usage(user)
space_usage_MB = byte_to_mb(user.space_usage)
space_quota_MB = byte_to_mb(user.space_quota)
row = [user_email, user_name, user_contact_email,
space_usage_MB, space_quota_MB]
data_list.append(row)
excel_name = "User-Storage.xlsx"
wb = write_xls('users', head, data_list)
wb.save(posixpath.join(path, excel_name)) if path else wb.save(excel_name)

View File

@@ -0,0 +1,65 @@
# Copyright (c) 2012-2016 Seafile Ltd.
# encoding: utf-8
import logging
import datetime
import posixpath
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
from seahub.utils import seafevents_api
from seahub.utils.ms_excel import write_xls
from seahub.utils.file_size import byte_to_mb
# Get an instance of a logger
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Export user traffic to '../User-Traffic.xlsx'."
def add_arguments(self, parser):
# Named (optional) arguments
parser.add_argument(
'--date',
help='Should be format of yyyymm, for example: 201907.',
)
parser.add_argument(
'--path',
help='Folder path to save the file. If not passed, Seafile will save the file in current folder.',
)
def handle(self, *args, **options):
path = options['path']
month = str(options['date'])
if not month:
self.stdout.write("month invalid.")
return
month_obj = datetime.datetime.strptime(month, "%Y%m")
res_data = seafevents_api.get_all_users_traffic_by_month(month_obj, -1, -1)
data_list = []
head = [_("Time"), _("User"), _("Web Download") + ('(MB)'), \
_("Sync Download") + ('(MB)'), _("Link Download") + ('(MB)'), \
_("Web Upload") + ('(MB)'), _("Sync Upload") + ('(MB)'), \
_("Link Upload") + ('(MB)')]
for data in res_data:
web_download = byte_to_mb(data['web_file_download'])
sync_download = byte_to_mb(data['sync_file_download'])
link_download = byte_to_mb(data['link_file_download'])
web_upload = byte_to_mb(data['web_file_upload'])
sync_upload = byte_to_mb(data['sync_file_upload'])
link_upload = byte_to_mb(data['link_file_upload'])
row = [month, data['user'], web_download, sync_download, \
link_download, web_upload, sync_upload, link_upload]
data_list.append(row)
excel_name = "User-Traffic-%s" % month
wb = write_xls(excel_name, head, data_list)
wb.save(posixpath.join(path, '%s.xlsx' % excel_name)) if path else wb.save('%s.xlsx' % excel_name)

View File

@@ -6,3 +6,6 @@ ONLYOFFICE_APIJS_URL = getattr(settings, 'ONLYOFFICE_APIJS_URL', '')
ONLYOFFICE_FILE_EXTENSION = getattr(settings, 'ONLYOFFICE_FILE_EXTENSION', ())
ONLYOFFICE_EDIT_FILE_EXTENSION = getattr(settings, 'ONLYOFFICE_EDIT_FILE_EXTENSION', ())
VERIFY_ONLYOFFICE_CERTIFICATE = getattr(settings, 'VERIFY_ONLYOFFICE_CERTIFICATE', True)
# if True, file will be saved when user click save btn on file editing page
ONLYOFFICE_FORCE_SAVE = getattr(settings, 'ONLYOFFICE_FORCE_SAVE', False)

View File

@@ -11,10 +11,16 @@ from django.utils.encoding import force_bytes
from seaserv import seafile_api
from seahub.utils import get_file_type_and_ext, gen_file_get_url, \
get_site_scheme_and_netloc
get_site_scheme_and_netloc, normalize_cache_key
from seahub.settings import ENABLE_WATERMARK
from seahub.onlyoffice.settings import ONLYOFFICE_APIJS_URL
from seahub.onlyoffice.settings import ONLYOFFICE_APIJS_URL, \
ONLYOFFICE_FORCE_SAVE
def generate_onlyoffice_cache_key(repo_id, file_path):
prefix = "ONLYOFFICE_"
value = "%s_%s" % (repo_id, file_path)
return normalize_cache_key(value, prefix)
def get_onlyoffice_dict(username, repo_id, file_path,
file_id='', can_edit=False, can_download=True):
@@ -47,8 +53,18 @@ def get_onlyoffice_dict(username, repo_id, file_path,
else:
document_type = 'text'
doc_info = json.dumps({'repo_id': repo_id, 'file_path': file_path, 'username': username})
cache_key = generate_onlyoffice_cache_key(repo_id, file_path)
doc_key = cache.get(cache_key)
# temporary solution when failed to get data from cache(django_pylibmc)
# when init process for the first time
if not doc_key:
doc_key = cache.get(cache_key)
if not doc_key:
doc_key = hashlib.md5(force_bytes(origin_repo_id + origin_file_path + file_id)).hexdigest()[:20]
doc_info = json.dumps({'repo_id': repo_id, 'file_path': file_path, 'username': username})
cache.set("ONLYOFFICE_%s" % doc_key, doc_info, None)
file_name = os.path.basename(file_path.rstrip('/'))
@@ -71,6 +87,7 @@ def get_onlyoffice_dict(username, repo_id, file_path,
'can_edit': can_edit,
'can_download': can_download,
'username': username,
'onlyoffice_force_save': ONLYOFFICE_FORCE_SAVE,
'enable_watermark': ENABLE_WATERMARK and not can_edit,
}

View File

@@ -10,6 +10,7 @@ from django.views.decorators.csrf import csrf_exempt
from seaserv import seafile_api
from seahub.onlyoffice.settings import VERIFY_ONLYOFFICE_CERTIFICATE
from seahub.onlyoffice.utils import generate_onlyoffice_cache_key
from seahub.utils import gen_inner_file_upload_url
# Get an instance of a logger
@@ -73,7 +74,11 @@ def onlyoffice_editor_callback(request):
post_data = json.loads(request.body)
status = int(post_data.get('status', -1))
# When forcesave is initiated, document editing service performs request to
# the callback handler with the link to the document as the url parameter and
# with the 6 value for the status parameter.
if status in (2, 6):
# Defines the link to the edited document to be saved with the document storage service.
# The link is present when the status value is equal to 2 or 3 only.
url = post_data.get('url')
@@ -90,6 +95,16 @@ def onlyoffice_editor_callback(request):
file_path = doc_info['file_path']
username = doc_info['username']
cache_key = generate_onlyoffice_cache_key(repo_id, file_path)
# cache document key when forcesave
if status == 6:
cache.set(cache_key, doc_key)
# remove document key from cache when document is ready for saving
# no one is editting
if status == 2:
cache.delete(cache_key)
fake_obj_id = {'online_office_update': True,}
update_token = seafile_api.get_fileserver_access_token(repo_id,
json.dumps(fake_obj_id), 'update', username)

View File

@@ -67,7 +67,6 @@
resumableUploadFileBlockSize: '{{ resumable_upload_file_block_size }}',
// storage backends
storages: (function () {
// for 'create repo' & 'storage backend' column in 'my libs'
var storages = [];
{% for storage in storages %}
storages.push({

View File

@@ -39,6 +39,9 @@ html, body { padding:0; margin:0; height:100%; }
"callbackUrl": "{{ callback_url }}",
"lang": "{{ LANGUAGE_CODE }}",
"mode": {% if can_edit %}"edit"{% else %}"view"{% endif %},
"customization": {
"forcesave": {% if onlyoffice_force_save %}true{% else %}false{% endif %},
},
"user": {
"name": "{{ username|email2nickname|escapejs }}"
}