mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-13 12:45:46 +00:00
Merge branch '7.1' into master
This commit is contained in:
commit
c29a571826
@ -34,7 +34,7 @@
|
|||||||
"react-responsive": "^6.1.2",
|
"react-responsive": "^6.1.2",
|
||||||
"react-select": "^2.4.1",
|
"react-select": "^2.4.1",
|
||||||
"reactstrap": "^6.4.0",
|
"reactstrap": "^6.4.0",
|
||||||
"seafile-js": "0.2.163",
|
"seafile-js": "0.2.164",
|
||||||
"socket.io-client": "^2.2.0",
|
"socket.io-client": "^2.2.0",
|
||||||
"unified": "^7.0.0",
|
"unified": "^7.0.0",
|
||||||
"url-parse": "^1.4.3",
|
"url-parse": "^1.4.3",
|
||||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { siteRoot, gettext, appAvatarURL } from '../../utils/constants';
|
import { siteRoot, gettext, appAvatarURL, logoutUrl } from '../../utils/constants';
|
||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@ -164,7 +164,7 @@ class Account extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<a href={siteRoot + 'profile/'} className="item">{gettext('Settings')}</a>
|
<a href={siteRoot + 'profile/'} className="item">{gettext('Settings')}</a>
|
||||||
{this.renderMenu()}
|
{this.renderMenu()}
|
||||||
<a href={siteRoot + 'accounts/logout/'} className="item">{gettext('Log out')}</a>
|
<a href={logoutUrl} className="item">{gettext('Log out')}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { siteRoot, gettext } from '../../utils/constants';
|
import { gettext, logoutUrl } from '../../utils/constants';
|
||||||
|
|
||||||
export default function Logout() {
|
export default function Logout() {
|
||||||
return (
|
return (
|
||||||
<a className="logout-icon" href={`${siteRoot}accounts/logout/`} title={gettext('Log out')}>
|
<a className="logout-icon" href={logoutUrl} title={gettext('Log out')}>
|
||||||
<i className="sf3-font sf3-font-logout" style={{fontSize: '24px'}}></i>
|
<i className="sf3-font sf3-font-logout" style={{fontSize: '24px'}}></i>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -43,7 +43,7 @@ class AboutDialog extends React.Component {
|
|||||||
<button type="button" className="close" onClick={this.toggle}><span aria-hidden="true">×</span></button>
|
<button type="button" className="close" onClick={this.toggle}><span aria-hidden="true">×</span></button>
|
||||||
<div className="about-content">
|
<div className="about-content">
|
||||||
<p><img src={mediaUrl + logoPath} height={logoHeight} width={logoWidth} title={siteTitle} alt="logo" /></p>
|
<p><img src={mediaUrl + logoPath} height={logoHeight} width={logoWidth} title={siteTitle} alt="logo" /></p>
|
||||||
<p>{gettext('Server Version: ')}{seafileVersion}<br />© 2020 {gettext('Seafile')}</p>
|
<p>{gettext('Server Version: ')}{seafileVersion}<br />© {(new Date()).getFullYear()} {gettext('Seafile')}</p>
|
||||||
<p>{this.renderExternalAboutLinks()}</p>
|
<p>{this.renderExternalAboutLinks()}</p>
|
||||||
<p><a href={href} target="_blank">{gettext('About Us')}</a></p>
|
<p><a href={href} target="_blank">{gettext('About Us')}</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import React from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { gettext } from '../../utils/constants';
|
|
||||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Table } from 'reactstrap';
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Table } from 'reactstrap';
|
||||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
|
||||||
import RoleEditor from '../select-editor/role-editor';
|
|
||||||
import UserSelect from '../user-select.js';
|
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import RoleEditor from '../select-editor/role-editor';
|
||||||
|
import UserSelect from '../user-select';
|
||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
|
import Loading from '../loading';
|
||||||
|
|
||||||
import '../../css/manage-members-dialog.css';
|
import '../../css/manage-members-dialog.css';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@ -21,13 +23,45 @@ class ManageMembersDialog extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isLoading: true, // first loading
|
||||||
|
isLoadingMore: false,
|
||||||
groupMembers: [],
|
groupMembers: [],
|
||||||
|
page: 1,
|
||||||
|
perPage: 100,
|
||||||
|
hasNextPage: false,
|
||||||
selectedOption: null,
|
selectedOption: null,
|
||||||
errMessage: [],
|
errMessage: [],
|
||||||
isItemFreezed: false,
|
isItemFreezed: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.listGroupMembers(this.state.page);
|
||||||
|
}
|
||||||
|
|
||||||
|
listGroupMembers = (page) => {
|
||||||
|
const { groupID } = this.props;
|
||||||
|
const { perPage, groupMembers } = this.state;
|
||||||
|
seafileAPI.listGroupMembers(groupID, page, perPage).then((res) => {
|
||||||
|
const members = res.data;
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
isLoadingMore: false,
|
||||||
|
page: page,
|
||||||
|
hasNextPage: members.length < perPage ? false : true,
|
||||||
|
groupMembers: groupMembers.concat(members)
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
isLoadingMore: false,
|
||||||
|
hasNextPage: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onSelectChange = (option) => {
|
onSelectChange = (option) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedOption: option,
|
selectedOption: option,
|
||||||
@ -41,8 +75,9 @@ class ManageMembersDialog extends React.Component {
|
|||||||
emails.push(this.state.selectedOption[i].email);
|
emails.push(this.state.selectedOption[i].email);
|
||||||
}
|
}
|
||||||
seafileAPI.addGroupMembers(this.props.groupID, emails).then((res) => {
|
seafileAPI.addGroupMembers(this.props.groupID, emails).then((res) => {
|
||||||
this.onGroupMembersChange();
|
const newMembers = res.data.success;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
groupMembers: [].concat(newMembers, this.state.groupMembers),
|
||||||
selectedOption: null,
|
selectedOption: null,
|
||||||
});
|
});
|
||||||
this.refs.userSelect.clearSelect();
|
this.refs.userSelect.clearSelect();
|
||||||
@ -57,21 +92,6 @@ class ManageMembersDialog extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
listGroupMembers = () => {
|
|
||||||
seafileAPI.listGroupMembers(this.props.groupID).then((res) => {
|
|
||||||
this.setState({
|
|
||||||
groupMembers: res.data
|
|
||||||
});
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onGroupMembersChange = () => {
|
|
||||||
this.listGroupMembers();
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleItemFreezed = (isFreezed) => {
|
toggleItemFreezed = (isFreezed) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isItemFreezed: isFreezed
|
isItemFreezed: isFreezed
|
||||||
@ -82,11 +102,43 @@ class ManageMembersDialog extends React.Component {
|
|||||||
this.props.toggleManageMembersDialog();
|
this.props.toggleManageMembersDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
handleScroll = (event) => {
|
||||||
this.listGroupMembers();
|
// isLoadingMore: to avoid repeated request
|
||||||
|
const { page, hasNextPage, isLoadingMore } = this.state;
|
||||||
|
if (hasNextPage && !isLoadingMore) {
|
||||||
|
const clientHeight = event.target.clientHeight;
|
||||||
|
const scrollHeight = event.target.scrollHeight;
|
||||||
|
const scrollTop = event.target.scrollTop;
|
||||||
|
const isBottom = (clientHeight + scrollTop + 1 >= scrollHeight);
|
||||||
|
if (isBottom) { // scroll to the bottom
|
||||||
|
this.setState({isLoadingMore: true}, () => {
|
||||||
|
this.listGroupMembers(page + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeMember = (targetMember) => {
|
||||||
|
this.setState({
|
||||||
|
groupMembers: this.state.groupMembers.map((item) => {
|
||||||
|
if (item.email == targetMember.email) {
|
||||||
|
item = targetMember;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMember = (targetMember) => {
|
||||||
|
const groupMembers = this.state.groupMembers;
|
||||||
|
groupMembers.splice(groupMembers.indexOf(targetMember), 1);
|
||||||
|
this.setState({
|
||||||
|
groupMembers: groupMembers
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { isLoading, hasNextPage } = this.state;
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={true} toggle={this.toggle}>
|
<Modal isOpen={true} toggle={this.toggle}>
|
||||||
<ModalHeader toggle={this.toggle}>{gettext('Manage group members')}</ModalHeader>
|
<ModalHeader toggle={this.toggle}>{gettext('Manage group members')}</ModalHeader>
|
||||||
@ -113,7 +165,9 @@ class ManageMembersDialog extends React.Component {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
<div className="manage-members">
|
<div className="manage-members" onScroll={this.handleScroll}>
|
||||||
|
{isLoading ? <Loading /> : (
|
||||||
|
<Fragment>
|
||||||
<Table size="sm" className="manage-members-table">
|
<Table size="sm" className="manage-members-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -126,23 +180,26 @@ class ManageMembersDialog extends React.Component {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{
|
{
|
||||||
this.state.groupMembers.length > 0 &&
|
this.state.groupMembers.length > 0 &&
|
||||||
this.state.groupMembers.map((item, index = 0) => {
|
this.state.groupMembers.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={index}>
|
|
||||||
<Member
|
<Member
|
||||||
|
key={index}
|
||||||
item={item}
|
item={item}
|
||||||
onGroupMembersChange={this.onGroupMembersChange}
|
changeMember={this.changeMember}
|
||||||
|
deleteMember={this.deleteMember}
|
||||||
groupID={this.props.groupID}
|
groupID={this.props.groupID}
|
||||||
isOwner={this.props.isOwner}
|
isOwner={this.props.isOwner}
|
||||||
isItemFreezed={this.state.isItemFreezed}
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
toggleItemFreezed={this.toggleItemFreezed}
|
toggleItemFreezed={this.toggleItemFreezed}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
{hasNextPage && <Loading />}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
@ -157,7 +214,8 @@ ManageMembersDialog.propTypes = propTypes;
|
|||||||
|
|
||||||
const MemberPropTypes = {
|
const MemberPropTypes = {
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
onGroupMembersChange: PropTypes.func.isRequired,
|
changeMember: PropTypes.func.isRequired,
|
||||||
|
deleteMember: PropTypes.func.isRequired,
|
||||||
groupID: PropTypes.string.isRequired,
|
groupID: PropTypes.string.isRequired,
|
||||||
isOwner: PropTypes.bool.isRequired,
|
isOwner: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
@ -175,16 +233,18 @@ class Member extends React.PureComponent {
|
|||||||
onChangeUserRole = (role) => {
|
onChangeUserRole = (role) => {
|
||||||
let isAdmin = role === 'Admin' ? 'True' : 'False';
|
let isAdmin = role === 'Admin' ? 'True' : 'False';
|
||||||
seafileAPI.setGroupAdmin(this.props.groupID, this.props.item.email, isAdmin).then((res) => {
|
seafileAPI.setGroupAdmin(this.props.groupID, this.props.item.email, isAdmin).then((res) => {
|
||||||
this.props.onGroupMembersChange();
|
this.props.changeMember(res.data);
|
||||||
});
|
}).catch(error => {
|
||||||
this.setState({
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
highlight: false,
|
toaster.danger(errMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteMember = (name) => {
|
deleteMember = () => {
|
||||||
seafileAPI.deleteGroupMember(this.props.groupID, name).then((res) => {
|
const { item } = this.props;
|
||||||
this.props.onGroupMembersChange();
|
seafileAPI.deleteGroupMember(this.props.groupID, item.email).then((res) => {
|
||||||
|
this.props.deleteMember(item);
|
||||||
|
toaster.success(gettext('Successfully deleted {name}.').replace('{name}', item.name));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
toaster.danger(errMessage);
|
toaster.danger(errMessage);
|
||||||
@ -243,8 +303,7 @@ class Member extends React.PureComponent {
|
|||||||
{(deleteAuthority && !this.props.isItemFreezed) &&
|
{(deleteAuthority && !this.props.isItemFreezed) &&
|
||||||
<i
|
<i
|
||||||
className="fa fa-times delete-group-member-icon"
|
className="fa fa-times delete-group-member-icon"
|
||||||
name={item.email}
|
onClick={this.deleteMember}>
|
||||||
onClick={this.deleteMember.bind(this, item.email)}>
|
|
||||||
</i>
|
</i>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
@ -171,8 +171,10 @@ class DirentListItem extends React.Component {
|
|||||||
|
|
||||||
onItemClick = (e) => {
|
onItemClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const dirent = this.props.dirent;
|
const dirent = this.props.dirent;
|
||||||
|
if (this.state.isRenameing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (Utils.imageCheck(dirent.name)) {
|
if (Utils.imageCheck(dirent.name)) {
|
||||||
this.props.showImagePopup(dirent);
|
this.props.showImagePopup(dirent);
|
||||||
} else {
|
} else {
|
||||||
@ -664,19 +666,19 @@ class DirentListItem extends React.Component {
|
|||||||
);
|
);
|
||||||
const mobileItem = (
|
const mobileItem = (
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td onClick={this.onItemClick}>
|
||||||
<div className="dir-icon">
|
<div className="dir-icon">
|
||||||
{dirent.encoded_thumbnail_src ?
|
{dirent.encoded_thumbnail_src ?
|
||||||
<img src={`${siteRoot}${dirent.encoded_thumbnail_src}`} className="thumbnail cursor-pointer" onClick={this.onItemClick} alt="" /> :
|
<img src={`${siteRoot}${dirent.encoded_thumbnail_src}`} className="thumbnail cursor-pointer" alt="" /> :
|
||||||
<img src={iconUrl} width="24" alt="" />
|
<img src={iconUrl} width="24" alt="" />
|
||||||
}
|
}
|
||||||
{dirent.is_locked && <img className="locked" src={mediaUrl + 'img/file-locked-32.png'} alt={gettext('locked')} title={lockedInfo}/>}
|
{dirent.is_locked && <img className="locked" src={mediaUrl + 'img/file-locked-32.png'} alt={gettext('locked')} title={lockedInfo}/>}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td onClick={this.onItemClick}>
|
||||||
{this.state.isRenameing ?
|
{this.state.isRenameing ?
|
||||||
<Rename hasSuffix={dirent.type !== 'dir'} name={dirent.name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
<Rename hasSuffix={dirent.type !== 'dir'} name={dirent.name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
||||||
<a href={dirent.type === 'dir' ? dirHref : fileHref} onClick={this.onItemClick}>{dirent.name}</a>
|
<a href={dirent.type === 'dir' ? dirHref : fileHref}>{dirent.name}</a>
|
||||||
}
|
}
|
||||||
<br />
|
<br />
|
||||||
{dirent.size && <span className="item-meta-info">{dirent.size}</span>}
|
{dirent.size && <span className="item-meta-info">{dirent.size}</span>}
|
||||||
|
@ -19,6 +19,11 @@ class SearchResultItem extends React.Component {
|
|||||||
let className = item.link_content ? 'item-img' : 'lib-item-img';
|
let className = item.link_content ? 'item-img' : 'lib-item-img';
|
||||||
let folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(true);
|
let folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(true);
|
||||||
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name, 192);
|
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name, 192);
|
||||||
|
|
||||||
|
if (item.thumbnail_url !== '') {
|
||||||
|
fileIconUrl = item.thumbnail_url;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="search-result-item" onClick={this.onClickHandler}>
|
<li className="search-result-item" onClick={this.onClickHandler}>
|
||||||
<img className={className} src={fileIconUrl} alt="" />
|
<img className={className} src={fileIconUrl} alt="" />
|
||||||
|
@ -178,6 +178,7 @@ class Search extends Component {
|
|||||||
items[i]['is_dir'] = data[i].is_dir;
|
items[i]['is_dir'] = data[i].is_dir;
|
||||||
items[i]['link_content'] = decodeURI(data[i].fullpath).substring(1);
|
items[i]['link_content'] = decodeURI(data[i].fullpath).substring(1);
|
||||||
items[i]['content'] = data[i].content_highlight;
|
items[i]['content'] = data[i].content_highlight;
|
||||||
|
items[i]['thumbnail_url'] = data[i].thumbnail_url;
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||||
import { Link } from '@reach/router';
|
import { Link, navigate } from '@reach/router';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { gettext, siteRoot, isPro, username, folderPermEnabled, isSystemStaff, enableResetEncryptedRepoPassword, isEmailConfigured } from '../../utils/constants';
|
import { gettext, siteRoot, isPro, username, folderPermEnabled, isSystemStaff, enableResetEncryptedRepoPassword, isEmailConfigured } from '../../utils/constants';
|
||||||
import ModalPortal from '../../components/modal-portal';
|
import ModalPortal from '../../components/modal-portal';
|
||||||
@ -479,14 +479,21 @@ class SharedRepoListItem extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitRepo = () => {
|
||||||
|
if (!this.state.isRenaming) {
|
||||||
|
navigate(this.repoURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderMobileUI = () => {
|
renderMobileUI = () => {
|
||||||
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
|
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
|
||||||
let { repo } = this.props;
|
let { repo } = this.props;
|
||||||
|
this.repoURL = libPath;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
|
||||||
<td><img src={iconUrl} title={iconTitle} width="24" alt={iconTitle}/></td>
|
<td onClick={this.visitRepo}><img src={iconUrl} title={iconTitle} width="24" alt={iconTitle}/></td>
|
||||||
<td>
|
<td onClick={this.visitRepo}>
|
||||||
{this.state.isRenaming ?
|
{this.state.isRenaming ?
|
||||||
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
||||||
<Link to={libPath}>{repo.repo_name}</Link>
|
<Link to={libPath}>{repo.repo_name}</Link>
|
||||||
|
@ -6,6 +6,7 @@ class SysAdminAdminUser {
|
|||||||
this.contact_email = object.contact_email;
|
this.contact_email = object.contact_email;
|
||||||
this.login_id = object.login_id;
|
this.login_id = object.login_id;
|
||||||
this.last_login = object.last_login;
|
this.last_login = object.last_login;
|
||||||
|
this.last_access_time = object.last_access_time;
|
||||||
this.create_time = object.create_time;
|
this.create_time = object.create_time;
|
||||||
this.is_active = object.is_active;
|
this.is_active = object.is_active;
|
||||||
this.is_staff = object.is_staff;
|
this.is_staff = object.is_staff;
|
||||||
|
@ -5,6 +5,7 @@ class SysAdminUser {
|
|||||||
this.contact_email = object.contact_email;
|
this.contact_email = object.contact_email;
|
||||||
this.login_id = object.login_id;
|
this.login_id = object.login_id;
|
||||||
this.last_login = object.last_login;
|
this.last_login = object.last_login;
|
||||||
|
this.last_access_time = object.last_access_time;
|
||||||
this.create_time = object.create_time;
|
this.create_time = object.create_time;
|
||||||
this.is_active = object.is_active;
|
this.is_active = object.is_active;
|
||||||
this.is_staff = object.is_staff;
|
this.is_staff = object.is_staff;
|
||||||
|
@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MediaQuery from 'react-responsive';
|
import MediaQuery from 'react-responsive';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Link } from '@reach/router';
|
import { Link, navigate } from '@reach/router';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { gettext, siteRoot, storages } from '../../utils/constants';
|
import { gettext, siteRoot, storages } from '../../utils/constants';
|
||||||
@ -121,6 +121,12 @@ class MylibRepoListItem extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitRepo = () => {
|
||||||
|
if (!this.state.isRenaming && this.props.repo.repo_name) {
|
||||||
|
navigate(this.repoURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onRepoClick = () => {
|
onRepoClick = () => {
|
||||||
this.props.onRepoClick(this.props.repo);
|
this.props.onRepoClick(this.props.repo);
|
||||||
}
|
}
|
||||||
@ -317,12 +323,12 @@ class MylibRepoListItem extends React.Component {
|
|||||||
let repo = this.props.repo;
|
let repo = this.props.repo;
|
||||||
let iconUrl = Utils.getLibIconUrl(repo);
|
let iconUrl = Utils.getLibIconUrl(repo);
|
||||||
let iconTitle = Utils.getLibIconTitle(repo);
|
let iconTitle = Utils.getLibIconTitle(repo);
|
||||||
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
let repoURL = this.repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onRepoClick}>
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onRepoClick}>
|
||||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
<td onClick={this.visitRepo}><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||||
<td>
|
<td onClick={this.visitRepo}>
|
||||||
{this.state.isRenaming && (
|
{this.state.isRenaming && (
|
||||||
<Rename
|
<Rename
|
||||||
name={repo.repo_name}
|
name={repo.repo_name}
|
||||||
|
@ -29,6 +29,11 @@ class ResultsItem extends React.Component {
|
|||||||
let linkContent = decodeURI(item.fullpath).substring(1);
|
let linkContent = decodeURI(item.fullpath).substring(1);
|
||||||
let folderIconUrl = linkContent ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(true);
|
let folderIconUrl = linkContent ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(true);
|
||||||
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name, 192);
|
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name, 192);
|
||||||
|
|
||||||
|
if (item.thumbnail_url !== '') {
|
||||||
|
fileIconUrl = item.thumbnail_url;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="search-result-item">
|
<li className="search-result-item">
|
||||||
<img className={linkContent ? 'item-img' : 'lib-item-img'} src={fileIconUrl} alt=""/>
|
<img className={linkContent ? 'item-img' : 'lib-item-img'} src={fileIconUrl} alt=""/>
|
||||||
|
@ -3,7 +3,7 @@ import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import cookie from 'react-cookies';
|
import cookie from 'react-cookies';
|
||||||
import { Link } from '@reach/router';
|
import { Link, navigate } from '@reach/router';
|
||||||
import { gettext, siteRoot, isPro } from '../../utils/constants';
|
import { gettext, siteRoot, isPro } from '../../utils/constants';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
@ -194,6 +194,10 @@ class Item extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitRepo = () => {
|
||||||
|
navigate(this.repoURL);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.unshared) {
|
if (this.state.unshared) {
|
||||||
return null;
|
return null;
|
||||||
@ -207,7 +211,7 @@ class Item extends Component {
|
|||||||
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
||||||
let shareIconClassName = 'op-icon sf2-icon-share repo-share-btn' + iconVisibility;
|
let shareIconClassName = 'op-icon sf2-icon-share repo-share-btn' + iconVisibility;
|
||||||
let leaveShareIconClassName = 'op-icon sf2-icon-x3' + iconVisibility;
|
let leaveShareIconClassName = 'op-icon sf2-icon-x3' + iconVisibility;
|
||||||
let shareRepoUrl =`${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`;
|
let shareRepoUrl = this.repoURL = `${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`;
|
||||||
const desktopItem = (
|
const desktopItem = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||||
@ -248,8 +252,8 @@ class Item extends Component {
|
|||||||
const mobileItem = (
|
const mobileItem = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||||
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
<td onClick={this.visitRepo}><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
||||||
<td>
|
<td onClick={this.visitRepo}>
|
||||||
<Link to={shareRepoUrl}>{data.repo_name}</Link><br />
|
<Link to={shareRepoUrl}>{data.repo_name}</Link><br />
|
||||||
<span className="item-meta-info" title={data.owner_contact_email}>{data.owner_name}</span>
|
<span className="item-meta-info" title={data.owner_contact_email}>{data.owner_name}</span>
|
||||||
<span className="item-meta-info">{data.size}</span>
|
<span className="item-meta-info">{data.size}</span>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
|
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||||
import { Link } from '@reach/router';
|
import { Link, navigate } from '@reach/router';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
@ -170,6 +170,15 @@ class Item extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitItem = () => {
|
||||||
|
const data = this.props.data;
|
||||||
|
if (data.is_dir) {
|
||||||
|
navigate(data.dirent_view_url);
|
||||||
|
} else {
|
||||||
|
window.open(data.dirent_view_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
if (this.state.unstarred) {
|
if (this.state.unstarred) {
|
||||||
@ -206,14 +215,14 @@ class Item extends Component {
|
|||||||
|
|
||||||
const mobileItem = (
|
const mobileItem = (
|
||||||
<tr>
|
<tr>
|
||||||
<td className="text-center">
|
<td className="text-center" onClick={this.visitItem}>
|
||||||
{
|
{
|
||||||
data.thumbnail_url ?
|
data.thumbnail_url ?
|
||||||
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
||||||
<img src={data.item_icon_url} alt={gettext('icon')} width="24" />
|
<img src={data.item_icon_url} alt={gettext('icon')} width="24" />
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td onClick={this.visitItem}>
|
||||||
{ data.is_dir ?
|
{ data.is_dir ?
|
||||||
<Link to={data.dirent_view_url}>{data.obj_name}</Link> :
|
<Link to={data.dirent_view_url}>{data.obj_name}</Link> :
|
||||||
<a className="normal" href={data.dirent_view_url} target="_blank">{data.obj_name}</a>
|
<a className="normal" href={data.dirent_view_url} target="_blank">{data.obj_name}</a>
|
||||||
|
@ -83,7 +83,7 @@ class Content extends Component {
|
|||||||
const colSpaceText = <Fragment>{spaceEl}{` / ${gettext('Quota')}`}</Fragment>;
|
const colSpaceText = <Fragment>{spaceEl}{` / ${gettext('Quota')}`}</Fragment>;
|
||||||
|
|
||||||
const colNameText = `${gettext('Name')} / ${gettext('Contact Email')}`;
|
const colNameText = `${gettext('Name')} / ${gettext('Contact Email')}`;
|
||||||
const colCreatedText = `${gettext('Created At')} / ${gettext('Last Login')}`;
|
const colCreatedText = `${gettext('Created At')} / ${gettext('Last Login')} / ${gettext('Last Access')}`;
|
||||||
if (isPro) {
|
if (isPro) {
|
||||||
columns.push(
|
columns.push(
|
||||||
{width: '20%', text: colNameText},
|
{width: '20%', text: colNameText},
|
||||||
@ -422,6 +422,8 @@ class Item extends Component {
|
|||||||
{`${item.create_time ? moment(item.create_time).format('YYYY-MM-DD HH:mm') : '--'} /`}
|
{`${item.create_time ? moment(item.create_time).format('YYYY-MM-DD HH:mm') : '--'} /`}
|
||||||
<br />
|
<br />
|
||||||
{`${item.last_login ? moment(item.last_login).fromNow() : '--'}`}
|
{`${item.last_login ? moment(item.last_login).fromNow() : '--'}`}
|
||||||
|
<br />
|
||||||
|
{`${item.last_access_time ? moment(item.last_access_time).fromNow() : '--'}`}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{(item.email != username && isOpIconShown) &&
|
{(item.email != username && isOpIconShown) &&
|
||||||
|
@ -3,6 +3,7 @@ export const gettext = window.gettext;
|
|||||||
|
|
||||||
export const siteRoot = window.app.config.siteRoot;
|
export const siteRoot = window.app.config.siteRoot;
|
||||||
export const loginUrl = window.app.config.loginUrl;
|
export const loginUrl = window.app.config.loginUrl;
|
||||||
|
export const logoutUrl = window.app.config.logoutUrl;
|
||||||
export const avatarInfo = window.app.config.avatarInfo;
|
export const avatarInfo = window.app.config.avatarInfo;
|
||||||
export const logoPath = window.app.config.logoPath;
|
export const logoPath = window.app.config.logoPath;
|
||||||
export const mediaUrl = window.app.config.mediaUrl;
|
export const mediaUrl = window.app.config.mediaUrl;
|
||||||
|
@ -484,7 +484,7 @@ export const Utils = {
|
|||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
|
||||||
getFileOperationList: function(currentRepoInfo, dirent, isContextmenu) {
|
getFileOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
|
||||||
let list = [];
|
let list = [];
|
||||||
const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK,
|
const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK,
|
||||||
COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT } = TextTranslation;
|
COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT } = TextTranslation;
|
||||||
@ -516,7 +516,7 @@ export const Utils = {
|
|||||||
|
|
||||||
if (isPro) {
|
if (isPro) {
|
||||||
if (dirent.is_locked) {
|
if (dirent.is_locked) {
|
||||||
if (dirent.locked_by_me || dirent.lock_owner == 'OnlineOffice') {
|
if (dirent.locked_by_me || dirent.lock_owner == 'OnlineOffice' || isRepoOwner || currentRepoInfo.is_admin) {
|
||||||
list.push(UNLOCK);
|
list.push(UNLOCK);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -551,7 +551,7 @@ export const Utils = {
|
|||||||
getDirentOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
|
getDirentOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
|
||||||
return dirent.type == 'dir' ?
|
return dirent.type == 'dir' ?
|
||||||
Utils.getFolderOperationList(isRepoOwner, currentRepoInfo, dirent, isContextmenu) :
|
Utils.getFolderOperationList(isRepoOwner, currentRepoInfo, dirent, isContextmenu) :
|
||||||
Utils.getFileOperationList(currentRepoInfo, dirent, isContextmenu);
|
Utils.getFileOperationList(isRepoOwner, currentRepoInfo, dirent, isContextmenu);
|
||||||
},
|
},
|
||||||
|
|
||||||
sharePerms: function(permission) {
|
sharePerms: function(permission) {
|
||||||
|
@ -2,7 +2,6 @@ Django==2.2.14
|
|||||||
future
|
future
|
||||||
captcha
|
captcha
|
||||||
django-statici18n
|
django-statici18n
|
||||||
django-post_office==3.3.0
|
|
||||||
django-webpack_loader
|
django-webpack_loader
|
||||||
gunicorn
|
gunicorn
|
||||||
mysqlclient
|
mysqlclient
|
||||||
|
@ -19,6 +19,7 @@ from seaserv import seafile_api, ccnet_api
|
|||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
from seahub.api2.utils import api_error, to_python_boolean
|
from seahub.api2.utils import api_error, to_python_boolean
|
||||||
|
from seahub.api2.models import TokenV2
|
||||||
|
|
||||||
import seahub.settings as settings
|
import seahub.settings as settings
|
||||||
from seahub.settings import SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, INIT_PASSWD, \
|
from seahub.settings import SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, INIT_PASSWD, \
|
||||||
@ -33,9 +34,12 @@ from seahub.profile.settings import CONTACT_CACHE_TIMEOUT, CONTACT_CACHE_PREFIX,
|
|||||||
from seahub.utils import is_valid_username2, is_org_context, \
|
from seahub.utils import is_valid_username2, is_org_context, \
|
||||||
is_pro_version, normalize_cache_key, is_valid_email, \
|
is_pro_version, normalize_cache_key, is_valid_email, \
|
||||||
IS_EMAIL_CONFIGURED, send_html_email, get_site_name, \
|
IS_EMAIL_CONFIGURED, send_html_email, get_site_name, \
|
||||||
gen_shared_link, gen_shared_upload_link
|
gen_shared_link, gen_shared_upload_link, \
|
||||||
|
get_file_audit_events, get_file_update_events
|
||||||
|
|
||||||
from seahub.utils.file_size import get_file_size_unit
|
from seahub.utils.file_size import get_file_size_unit
|
||||||
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr
|
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
|
||||||
|
datetime_to_isoformat_timestr, utc_to_local
|
||||||
from seahub.utils.user_permissions import get_user_role
|
from seahub.utils.user_permissions import get_user_role
|
||||||
from seahub.utils.repo import normalize_repo_status_code
|
from seahub.utils.repo import normalize_repo_status_code
|
||||||
from seahub.constants import DEFAULT_ADMIN
|
from seahub.constants import DEFAULT_ADMIN
|
||||||
@ -57,6 +61,43 @@ logger = logging.getLogger(__name__)
|
|||||||
json_content_type = 'application/json; charset=utf-8'
|
json_content_type = 'application/json; charset=utf-8'
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_last_access_time(email, last_login_time):
|
||||||
|
|
||||||
|
device_last_access = ''
|
||||||
|
audit_last_access = ''
|
||||||
|
update_last_access = ''
|
||||||
|
|
||||||
|
devices = TokenV2.objects.filter(user=email).order_by('-last_accessed')
|
||||||
|
if devices:
|
||||||
|
device_last_access = devices[0].last_accessed
|
||||||
|
|
||||||
|
audit_events = get_file_audit_events(email, 0, None, 0, 1) or []
|
||||||
|
if audit_events:
|
||||||
|
audit_last_access = audit_events[0].timestamp
|
||||||
|
|
||||||
|
update_events = get_file_update_events(email, 0, None, 0, 1) or []
|
||||||
|
if update_events:
|
||||||
|
update_last_access = update_events[0].timestamp
|
||||||
|
|
||||||
|
last_access_time_list = []
|
||||||
|
if last_login_time:
|
||||||
|
last_access_time_list.append(last_login_time)
|
||||||
|
|
||||||
|
if device_last_access:
|
||||||
|
last_access_time_list.append(device_last_access)
|
||||||
|
|
||||||
|
if audit_last_access:
|
||||||
|
last_access_time_list.append(utc_to_local(audit_last_access))
|
||||||
|
|
||||||
|
if update_last_access:
|
||||||
|
last_access_time_list.append(utc_to_local(update_last_access))
|
||||||
|
|
||||||
|
if not last_access_time_list:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return datetime_to_isoformat_timestr(sorted(last_access_time_list)[-1])
|
||||||
|
|
||||||
|
|
||||||
def get_user_upload_link_info(uls):
|
def get_user_upload_link_info(uls):
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
@ -204,6 +245,7 @@ def update_user_info(request, user, password, is_active, is_staff, role,
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
seafile_api.set_user_quota(email, -1)
|
seafile_api.set_user_quota(email, -1)
|
||||||
|
|
||||||
|
|
||||||
def get_user_info(email):
|
def get_user_info(email):
|
||||||
|
|
||||||
user = User.objects.get(email=email)
|
user = User.objects.get(email=email)
|
||||||
@ -297,8 +339,15 @@ class AdminAdminUsers(APIView):
|
|||||||
user_info['quota_total'] = -1
|
user_info['quota_total'] = -1
|
||||||
|
|
||||||
user_info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
user_info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
||||||
|
|
||||||
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
||||||
user_info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
|
if last_login_obj:
|
||||||
|
user_info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
|
||||||
|
user_info['last_access_time'] = get_user_last_access_time(user.email,
|
||||||
|
last_login_obj.last_login)
|
||||||
|
else:
|
||||||
|
user_info['last_login'] = ''
|
||||||
|
user_info['last_access_time'] = get_user_last_access_time(user.email, '')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
admin_role = AdminRole.objects.get_admin_role(user.email)
|
admin_role = AdminRole.objects.get_admin_role(user.email)
|
||||||
@ -312,6 +361,7 @@ class AdminAdminUsers(APIView):
|
|||||||
}
|
}
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
|
|
||||||
class AdminUsers(APIView):
|
class AdminUsers(APIView):
|
||||||
|
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
@ -363,7 +413,13 @@ class AdminUsers(APIView):
|
|||||||
info['quota_total'] = seafile_api.get_user_quota(user.email)
|
info['quota_total'] = seafile_api.get_user_quota(user.email)
|
||||||
|
|
||||||
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
||||||
info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
|
if last_login_obj:
|
||||||
|
info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
|
||||||
|
info['last_access_time'] = get_user_last_access_time(user.email,
|
||||||
|
last_login_obj.last_login)
|
||||||
|
else:
|
||||||
|
info['last_login'] = ''
|
||||||
|
info['last_access_time'] = get_user_last_access_time(user.email, '')
|
||||||
|
|
||||||
info['role'] = get_user_role(user)
|
info['role'] = get_user_role(user)
|
||||||
|
|
||||||
@ -423,8 +479,10 @@ class AdminUsers(APIView):
|
|||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self.get_info_of_users_order_by_quota_usage(source, direction,
|
data = self.get_info_of_users_order_by_quota_usage(source,
|
||||||
page, per_page)
|
direction,
|
||||||
|
page,
|
||||||
|
per_page)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
@ -448,8 +506,10 @@ class AdminUsers(APIView):
|
|||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self.get_info_of_users_order_by_quota_usage(source, direction,
|
data = self.get_info_of_users_order_by_quota_usage(source,
|
||||||
page, per_page)
|
direction,
|
||||||
|
page,
|
||||||
|
per_page)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
@ -489,10 +549,19 @@ class AdminUsers(APIView):
|
|||||||
info['quota_usage'] = -1
|
info['quota_usage'] = -1
|
||||||
info['quota_total'] = -1
|
info['quota_total'] = -1
|
||||||
|
|
||||||
info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
|
||||||
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
|
||||||
info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
|
|
||||||
info['role'] = get_user_role(user)
|
info['role'] = get_user_role(user)
|
||||||
|
|
||||||
|
info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
||||||
|
|
||||||
|
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
||||||
|
if last_login_obj:
|
||||||
|
info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
|
||||||
|
info['last_access_time'] = get_user_last_access_time(user.email,
|
||||||
|
last_login_obj.last_login)
|
||||||
|
else:
|
||||||
|
info['last_login'] = ''
|
||||||
|
info['last_access_time'] = get_user_last_access_time(user.email, '')
|
||||||
|
|
||||||
if getattr(settings, 'MULTI_INSTITUTION', False):
|
if getattr(settings, 'MULTI_INSTITUTION', False):
|
||||||
info['institution'] = profile.institution if profile else ''
|
info['institution'] = profile.institution if profile else ''
|
||||||
|
|
||||||
@ -569,8 +638,7 @@ class AdminUsers(APIView):
|
|||||||
|
|
||||||
if is_org_context(request):
|
if is_org_context(request):
|
||||||
org_id = request.user.org.org_id
|
org_id = request.user.org.org_id
|
||||||
org_quota_mb = seafile_api.get_org_quota(org_id) / \
|
org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB')
|
||||||
get_file_size_unit('MB')
|
|
||||||
|
|
||||||
if quota_total_mb > org_quota_mb:
|
if quota_total_mb > org_quota_mb:
|
||||||
error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
|
error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
|
||||||
@ -611,6 +679,7 @@ class AdminUsers(APIView):
|
|||||||
c,
|
c,
|
||||||
None,
|
None,
|
||||||
[email2contact_email(email)])
|
[email2contact_email(email)])
|
||||||
|
|
||||||
add_user_tip = _('Successfully added user %(user)s. An email notification has been sent.') % {'user': email}
|
add_user_tip = _('Successfully added user %(user)s. An email notification has been sent.') % {'user': email}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
@ -669,8 +738,16 @@ class AdminLDAPUsers(APIView):
|
|||||||
info['quota_total'] = seafile_api.get_user_quota(user.email)
|
info['quota_total'] = seafile_api.get_user_quota(user.email)
|
||||||
info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
|
info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
|
||||||
info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
||||||
|
|
||||||
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
||||||
info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
|
if last_login_obj:
|
||||||
|
info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
|
||||||
|
info['last_access_time'] = get_user_last_access_time(user.email,
|
||||||
|
last_login_obj.last_login)
|
||||||
|
else:
|
||||||
|
info['last_login'] = ''
|
||||||
|
info['last_access_time'] = get_user_last_access_time(user.email, '')
|
||||||
|
|
||||||
data.append(info)
|
data.append(info)
|
||||||
|
|
||||||
result = {'ldap_user_list': data, 'has_next_page': has_next_page}
|
result = {'ldap_user_list': data, 'has_next_page': has_next_page}
|
||||||
@ -859,8 +936,16 @@ class AdminSearchUser(APIView):
|
|||||||
info['quota_total'] = seafile_api.get_user_quota(user.email)
|
info['quota_total'] = seafile_api.get_user_quota(user.email)
|
||||||
|
|
||||||
info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
||||||
|
|
||||||
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
last_login_obj = UserLastLogin.objects.get_by_username(user.email)
|
||||||
info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login) if last_login_obj else ''
|
if last_login_obj:
|
||||||
|
info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
|
||||||
|
info['last_access_time'] = get_user_last_access_time(user.email,
|
||||||
|
last_login_obj.last_login)
|
||||||
|
else:
|
||||||
|
info['last_login'] = ''
|
||||||
|
info['last_access_time'] = get_user_last_access_time(user.email, '')
|
||||||
|
|
||||||
info['role'] = get_user_role(user)
|
info['role'] = get_user_role(user)
|
||||||
|
|
||||||
if getattr(settings, 'MULTI_INSTITUTION', False):
|
if getattr(settings, 'MULTI_INSTITUTION', False):
|
||||||
@ -984,8 +1069,7 @@ class AdminUser(APIView):
|
|||||||
|
|
||||||
if is_org_context(request):
|
if is_org_context(request):
|
||||||
org_id = request.user.org.org_id
|
org_id = request.user.org.org_id
|
||||||
org_quota_mb = seafile_api.get_org_quota(org_id) / \
|
org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB')
|
||||||
get_file_size_unit('MB')
|
|
||||||
|
|
||||||
if quota_total_mb > org_quota_mb:
|
if quota_total_mb > org_quota_mb:
|
||||||
error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
|
error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
|
||||||
|
@ -15,7 +15,6 @@ from django.utils.translation import ugettext as _
|
|||||||
|
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
|
|
||||||
import seahub.settings as settings
|
|
||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
from seahub.api2.utils import api_error
|
from seahub.api2.utils import api_error
|
||||||
@ -228,7 +227,7 @@ class AdminUsersBatch(APIView):
|
|||||||
|
|
||||||
if institution != '':
|
if institution != '':
|
||||||
try:
|
try:
|
||||||
obj_insti = Institution.objects.get(name=institution)
|
Institution.objects.get(name=institution)
|
||||||
except Institution.DoesNotExist:
|
except Institution.DoesNotExist:
|
||||||
error_msg = 'Institution %s does not exist' % institution
|
error_msg = 'Institution %s does not exist' % institution
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
@ -372,13 +371,13 @@ class AdminImportUsers(APIView):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
send_html_email_with_dj_template(
|
send_html_email_with_dj_template(email,
|
||||||
email, dj_template='sysadmin/user_batch_add_email.html',
|
|
||||||
subject=_('You are invited to join %s') % get_site_name(),
|
subject=_('You are invited to join %s') % get_site_name(),
|
||||||
|
dj_template='sysadmin/user_batch_add_email.html',
|
||||||
context={
|
context={
|
||||||
'user': email2nickname(request.user.username),
|
'user': email2nickname(request.user.username),
|
||||||
'email': email,
|
'email': email,
|
||||||
'password': password,
|
'password': password
|
||||||
})
|
})
|
||||||
|
|
||||||
user = User.objects.get(email=email)
|
user = User.objects.get(email=email)
|
||||||
@ -407,4 +406,3 @@ class AdminImportUsers(APIView):
|
|||||||
operation=USER_ADD, detail=admin_op_detail)
|
operation=USER_ADD, detail=admin_op_detail)
|
||||||
|
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from seahub.views import check_folder_permission
|
|||||||
from seahub.utils.file_op import check_file_lock, if_locked_by_online_office
|
from seahub.utils.file_op import check_file_lock, if_locked_by_online_office
|
||||||
from seahub.views.file import can_preview_file, can_edit_file
|
from seahub.views.file import can_preview_file, can_edit_file
|
||||||
from seahub.constants import PERMISSION_READ_WRITE
|
from seahub.constants import PERMISSION_READ_WRITE
|
||||||
from seahub.utils.repo import parse_repo_perm
|
from seahub.utils.repo import parse_repo_perm, is_repo_admin, is_repo_owner
|
||||||
from seahub.utils.file_types import MARKDOWN, TEXT
|
from seahub.utils.file_types import MARKDOWN, TEXT
|
||||||
|
|
||||||
from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, \
|
from seahub.settings import MAX_UPLOAD_FILE_NAME_LEN, \
|
||||||
@ -295,11 +295,9 @@ class FileView(APIView):
|
|||||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
# rename file
|
# rename file
|
||||||
new_file_name = check_filename_with_rename(repo_id, parent_dir,
|
new_file_name = check_filename_with_rename(repo_id, parent_dir, new_file_name)
|
||||||
new_file_name)
|
|
||||||
try:
|
try:
|
||||||
seafile_api.rename_file(repo_id, parent_dir, oldname,
|
seafile_api.rename_file(repo_id, parent_dir, oldname, new_file_name, username)
|
||||||
new_file_name, username)
|
|
||||||
except SearpcError as e:
|
except SearpcError as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
@ -586,7 +584,9 @@ class FileView(APIView):
|
|||||||
error_msg = _("File is not locked.")
|
error_msg = _("File is not locked.")
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
if locked_by_me or locked_by_online_office:
|
if locked_by_me or locked_by_online_office or \
|
||||||
|
is_repo_owner(request, repo_id, username) or \
|
||||||
|
is_repo_admin(username, repo_id):
|
||||||
# unlock file
|
# unlock file
|
||||||
try:
|
try:
|
||||||
seafile_api.unlock_file(repo_id, path)
|
seafile_api.unlock_file(repo_id, path)
|
||||||
|
@ -15,13 +15,13 @@ from seahub.api2.throttling import UserRateThrottle
|
|||||||
from seahub.api2.utils import api_error
|
from seahub.api2.utils import api_error
|
||||||
from seahub.invitations.models import Invitation
|
from seahub.invitations.models import Invitation
|
||||||
from seahub.base.accounts import User
|
from seahub.base.accounts import User
|
||||||
from post_office.models import STATUS
|
from seahub.utils.mail import send_html_email_with_dj_template
|
||||||
from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
|
|
||||||
from seahub.utils import get_site_name
|
from seahub.utils import get_site_name
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
json_content_type = 'application/json; charset=utf-8'
|
json_content_type = 'application/json; charset=utf-8'
|
||||||
|
|
||||||
|
|
||||||
def invitation_owner_check(func):
|
def invitation_owner_check(func):
|
||||||
"""Check whether user is the invitation inviter.
|
"""Check whether user is the invitation inviter.
|
||||||
"""
|
"""
|
||||||
@ -34,6 +34,7 @@ def invitation_owner_check(func):
|
|||||||
|
|
||||||
return _decorated
|
return _decorated
|
||||||
|
|
||||||
|
|
||||||
class InvitationView(APIView):
|
class InvitationView(APIView):
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated, CanInviteGuest)
|
permission_classes = (IsAuthenticated, CanInviteGuest)
|
||||||
@ -108,13 +109,12 @@ class InvitationRevokeView(APIView):
|
|||||||
'site_name': site_name,
|
'site_name': site_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
m = send_html_email_with_dj_template(
|
send_success = send_html_email_with_dj_template(email,
|
||||||
email, dj_template='invitations/invitation_revoke_email.html',
|
|
||||||
subject=subject,
|
subject=subject,
|
||||||
context=context,
|
dj_template='invitations/invitation_revoke_email.html',
|
||||||
priority=MAIL_PRIORITY.now
|
context=context)
|
||||||
)
|
|
||||||
if m.status != STATUS.sent:
|
if not send_success:
|
||||||
logger.warning('send revoke access email to %s failed')
|
logger.warning('send revoke access email to %s failed')
|
||||||
|
|
||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
|
@ -6,7 +6,6 @@ from rest_framework.authentication import SessionAuthentication
|
|||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from post_office.models import STATUS
|
|
||||||
|
|
||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.api2.permissions import CanInviteGuest
|
from seahub.api2.permissions import CanInviteGuest
|
||||||
@ -70,8 +69,9 @@ class InvitationsView(APIView):
|
|||||||
|
|
||||||
i = Invitation.objects.add(inviter=request.user.username,
|
i = Invitation.objects.add(inviter=request.user.username,
|
||||||
accepter=accepter)
|
accepter=accepter)
|
||||||
m = i.send_to(email=accepter)
|
send_success = i.send_to(email=accepter)
|
||||||
if m.status == STATUS.sent:
|
|
||||||
|
if send_success:
|
||||||
return Response(i.to_dict(), status=201)
|
return Response(i.to_dict(), status=201)
|
||||||
else:
|
else:
|
||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
@ -120,10 +120,12 @@ class InvitationsBatchView(APIView):
|
|||||||
|
|
||||||
if Invitation.objects.filter(inviter=request.user.username,
|
if Invitation.objects.filter(inviter=request.user.username,
|
||||||
accepter=accepter).count() > 0:
|
accepter=accepter).count() > 0:
|
||||||
|
|
||||||
result['failed'].append({
|
result['failed'].append({
|
||||||
'email': accepter,
|
'email': accepter,
|
||||||
'error_msg': _('%s is already invited.') % accepter
|
'error_msg': _('%s is already invited.') % accepter
|
||||||
})
|
})
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -141,11 +143,13 @@ class InvitationsBatchView(APIView):
|
|||||||
i = Invitation.objects.add(inviter=request.user.username,
|
i = Invitation.objects.add(inviter=request.user.username,
|
||||||
accepter=accepter)
|
accepter=accepter)
|
||||||
|
|
||||||
m = i.send_to(email=accepter)
|
send_success = i.send_to(email=accepter)
|
||||||
if m.status != STATUS.sent:
|
|
||||||
|
if not send_success:
|
||||||
result['failed'].append({
|
result['failed'].append({
|
||||||
'email': accepter,
|
'email': accepter,
|
||||||
'error_msg': _('Failed to send email, email service is not properly configured, please contact administrator.'),
|
'error_msg': _('Failed to send email, email service is not properly configured, \
|
||||||
|
please contact administrator.'),
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
result['success'].append(i.to_dict())
|
result['success'].append(i.to_dict())
|
||||||
|
@ -6,7 +6,6 @@ from rest_framework.authentication import SessionAuthentication
|
|||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
|
|
||||||
@ -14,8 +13,7 @@ from seahub.api2.authentication import TokenAuthentication
|
|||||||
from seahub.api2.permissions import CanInviteGuest
|
from seahub.api2.permissions import CanInviteGuest
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
from seahub.api2.utils import api_error
|
from seahub.api2.utils import api_error
|
||||||
from seahub.invitations.models import Invitation, RepoShareInvitation
|
from seahub.invitations.models import RepoShareInvitation
|
||||||
from post_office.models import STATUS
|
|
||||||
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
|
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
|
||||||
from seahub.share.utils import is_repo_admin
|
from seahub.share.utils import is_repo_admin
|
||||||
from seahub.utils import is_org_context
|
from seahub.utils import is_org_context
|
||||||
|
@ -8,7 +8,6 @@ from rest_framework.authentication import SessionAuthentication
|
|||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from post_office.models import STATUS
|
|
||||||
|
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
|
|
||||||
@ -20,7 +19,6 @@ from seahub.base.accounts import User
|
|||||||
from seahub.utils import is_valid_email
|
from seahub.utils import is_valid_email
|
||||||
from seahub.invitations.models import Invitation, RepoShareInvitation
|
from seahub.invitations.models import Invitation, RepoShareInvitation
|
||||||
from seahub.invitations.utils import block_accepter
|
from seahub.invitations.utils import block_accepter
|
||||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
|
||||||
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE, GUEST_USER
|
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE, GUEST_USER
|
||||||
from seahub.share.utils import is_repo_admin
|
from seahub.share.utils import is_repo_admin
|
||||||
from seahub.utils import is_org_context
|
from seahub.utils import is_org_context
|
||||||
@ -29,6 +27,7 @@ from seahub.base.templatetags.seahub_tags import email2nickname
|
|||||||
json_content_type = 'application/json; charset=utf-8'
|
json_content_type = 'application/json; charset=utf-8'
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RepoShareInvitationsView(APIView):
|
class RepoShareInvitationsView(APIView):
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated, CanInviteGuest)
|
permission_classes = (IsAuthenticated, CanInviteGuest)
|
||||||
@ -203,11 +202,13 @@ class RepoShareInvitationsBatchView(APIView):
|
|||||||
|
|
||||||
result['success'].append(data)
|
result['success'].append(data)
|
||||||
|
|
||||||
m = invitation.send_to(email=accepter)
|
send_sucess = invitation.send_to(email=accepter)
|
||||||
if m.status != STATUS.sent:
|
|
||||||
|
if not send_sucess:
|
||||||
result['failed'].append({
|
result['failed'].append({
|
||||||
'email': accepter,
|
'email': accepter,
|
||||||
'error_msg': _('Failed to send email, email service is not properly configured, please contact administrator.'),
|
'error_msg': _('Failed to send email, email service is not properly configured, \
|
||||||
|
please contact administrator.'),
|
||||||
})
|
})
|
||||||
|
|
||||||
return Response(result)
|
return Response(result)
|
||||||
|
@ -72,6 +72,7 @@ from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \
|
|||||||
gen_shared_upload_link, convert_cmmt_desc_link, is_valid_dirent_name, \
|
gen_shared_upload_link, convert_cmmt_desc_link, is_valid_dirent_name, \
|
||||||
normalize_file_path, get_no_duplicate_obj_name, normalize_dir_path
|
normalize_file_path, get_no_duplicate_obj_name, normalize_dir_path
|
||||||
|
|
||||||
|
from seahub.utils.file_types import IMAGE
|
||||||
from seahub.utils.file_revisions import get_file_revisions_after_renamed
|
from seahub.utils.file_revisions import get_file_revisions_after_renamed
|
||||||
from seahub.utils.devices import do_unlink_device
|
from seahub.utils.devices import do_unlink_device
|
||||||
from seahub.utils.repo import get_repo_owner, get_library_storages, \
|
from seahub.utils.repo import get_repo_owner, get_library_storages, \
|
||||||
@ -424,6 +425,7 @@ class Search(APIView):
|
|||||||
throttle_classes = (UserRateThrottle, )
|
throttle_classes = (UserRateThrottle, )
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
|
|
||||||
if not HAS_FILE_SEARCH:
|
if not HAS_FILE_SEARCH:
|
||||||
error_msg = 'Search not supported.'
|
error_msg = 'Search not supported.'
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
@ -601,6 +603,16 @@ class Search(APIView):
|
|||||||
else:
|
else:
|
||||||
e['repo_type'] = ''
|
e['repo_type'] = ''
|
||||||
|
|
||||||
|
e['thumbnail_url'] = ''
|
||||||
|
filetype, fileext = get_file_type_and_ext(e.get('name', ''))
|
||||||
|
|
||||||
|
if filetype == IMAGE:
|
||||||
|
thumbnail_url = reverse('api2-thumbnail',
|
||||||
|
args=[e.get('repo_id', '')],
|
||||||
|
request=request)
|
||||||
|
params = '?p={}&size={}'.format(quote(e.get('fullpath', '').encode('utf-8')), 72)
|
||||||
|
e['thumbnail_url'] = thumbnail_url + params
|
||||||
|
|
||||||
has_more = True if total > current_page * per_page else False
|
has_more = True if total > current_page * per_page else False
|
||||||
return Response({"total":total, "results":results, "has_more":has_more})
|
return Response({"total":total, "results":results, "has_more":has_more})
|
||||||
|
|
||||||
@ -5054,7 +5066,7 @@ class OfficeGenerateView(APIView):
|
|||||||
return HttpResponse(json.dumps(ret_dict), status=200, content_type=json_content_type)
|
return HttpResponse(json.dumps(ret_dict), status=200, content_type=json_content_type)
|
||||||
|
|
||||||
class ThumbnailView(APIView):
|
class ThumbnailView(APIView):
|
||||||
authentication_classes = (TokenAuthentication,)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
throttle_classes = (UserRateThrottle, )
|
throttle_classes = (UserRateThrottle, )
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ from django.utils.encoding import smart_text
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.sites.shortcuts import get_current_site
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
import seaserv
|
|
||||||
from seaserv import ccnet_threaded_rpc, unset_repo_passwd, \
|
from seaserv import ccnet_threaded_rpc, unset_repo_passwd, \
|
||||||
seafile_api, ccnet_api
|
seafile_api, ccnet_api
|
||||||
from constance import config
|
from constance import config
|
||||||
@ -24,7 +23,7 @@ from seahub.role_permissions.utils import get_enabled_role_permissions_by_role,
|
|||||||
get_enabled_admin_role_permissions_by_role
|
get_enabled_admin_role_permissions_by_role
|
||||||
from seahub.utils import is_user_password_strong, get_site_name, \
|
from seahub.utils import is_user_password_strong, get_site_name, \
|
||||||
clear_token, get_system_admins, is_pro_version, IS_EMAIL_CONFIGURED
|
clear_token, get_system_admins, is_pro_version, IS_EMAIL_CONFIGURED
|
||||||
from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
|
from seahub.utils.mail import send_html_email_with_dj_template
|
||||||
from seahub.utils.licenseparse import user_number_over_limit
|
from seahub.utils.licenseparse import user_number_over_limit
|
||||||
from seahub.share.models import ExtraSharePermission
|
from seahub.share.models import ExtraSharePermission
|
||||||
|
|
||||||
@ -43,6 +42,7 @@ ANONYMOUS_EMAIL = 'Anonymous'
|
|||||||
|
|
||||||
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
|
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
|
||||||
|
|
||||||
|
|
||||||
class UserManager(object):
|
class UserManager(object):
|
||||||
|
|
||||||
def create_user(self, email, password=None, is_staff=False, is_active=False):
|
def create_user(self, email, password=None, is_staff=False, is_active=False):
|
||||||
@ -122,6 +122,7 @@ class UserManager(object):
|
|||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
class UserPermissions(object):
|
class UserPermissions(object):
|
||||||
def __init__(self, user):
|
def __init__(self, user):
|
||||||
self.user = user
|
self.user = user
|
||||||
@ -222,6 +223,7 @@ class UserPermissions(object):
|
|||||||
|
|
||||||
return self._get_perm_by_roles('can_publish_repo')
|
return self._get_perm_by_roles('can_publish_repo')
|
||||||
|
|
||||||
|
|
||||||
class AdminPermissions(object):
|
class AdminPermissions(object):
|
||||||
def __init__(self, user):
|
def __init__(self, user):
|
||||||
self.user = user
|
self.user = user
|
||||||
@ -322,8 +324,8 @@ class User(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
emailuser = ccnet_threaded_rpc.get_emailuser(self.username)
|
emailuser = ccnet_api.get_emailuser(self.username)
|
||||||
if emailuser:
|
if emailuser and emailuser.source.lower() in ("db", "ldapimport"):
|
||||||
if not hasattr(self, 'password'):
|
if not hasattr(self, 'password'):
|
||||||
self.set_unusable_password()
|
self.set_unusable_password()
|
||||||
|
|
||||||
@ -385,12 +387,10 @@ class User(object):
|
|||||||
if orgs:
|
if orgs:
|
||||||
for org in orgs:
|
for org in orgs:
|
||||||
org_id = org.org_id
|
org_id = org.org_id
|
||||||
shared_in_repos = seafile_api.get_org_share_in_repo_list(org_id,
|
shared_in_repos = seafile_api.get_org_share_in_repo_list(org_id, username, -1, -1)
|
||||||
username, -1, -1)
|
|
||||||
|
|
||||||
for r in shared_in_repos:
|
for r in shared_in_repos:
|
||||||
seafile_api.org_remove_share(org_id,
|
seafile_api.org_remove_share(org_id, r.repo_id, r.user, username)
|
||||||
r.repo_id, r.user, username)
|
|
||||||
else:
|
else:
|
||||||
shared_in_repos = seafile_api.get_share_in_repo_list(username, -1, -1)
|
shared_in_repos = seafile_api.get_share_in_repo_list(username, -1, -1)
|
||||||
for r in shared_in_repos:
|
for r in shared_in_repos:
|
||||||
@ -478,15 +478,12 @@ class User(object):
|
|||||||
user_language = Profile.objects.get_user_language(u.email)
|
user_language = Profile.objects.get_user_language(u.email)
|
||||||
translation.activate(user_language)
|
translation.activate(user_language)
|
||||||
|
|
||||||
send_html_email_with_dj_template(
|
send_html_email_with_dj_template(u.email,
|
||||||
u.email, dj_template='sysadmin/user_freeze_email.html',
|
|
||||||
subject=_('Account %(account)s froze on %(site)s.') % {
|
subject=_('Account %(account)s froze on %(site)s.') % {
|
||||||
"account": self.email,
|
"account": self.email,
|
||||||
"site": get_site_name(),
|
"site": get_site_name()},
|
||||||
},
|
dj_template='sysadmin/user_freeze_email.html',
|
||||||
context={'user': self.email},
|
context={'user': self.email})
|
||||||
priority=MAIL_PRIORITY.now
|
|
||||||
)
|
|
||||||
|
|
||||||
# restore current language
|
# restore current language
|
||||||
translation.activate(cur_language)
|
translation.activate(cur_language)
|
||||||
@ -535,10 +532,11 @@ class User(object):
|
|||||||
for r in passwd_setted_repos:
|
for r in passwd_setted_repos:
|
||||||
unset_repo_passwd(r.id, self.email)
|
unset_repo_passwd(r.id, self.email)
|
||||||
|
|
||||||
|
|
||||||
class AuthBackend(object):
|
class AuthBackend(object):
|
||||||
|
|
||||||
def get_user_with_import(self, username):
|
def get_user_with_import(self, username):
|
||||||
emailuser = seaserv.get_emailuser_with_import(username)
|
emailuser = ccnet_api.get_emailuser_with_import(username)
|
||||||
if not emailuser:
|
if not emailuser:
|
||||||
raise User.DoesNotExist('User matching query does not exits.')
|
raise User.DoesNotExist('User matching query does not exits.')
|
||||||
|
|
||||||
@ -580,7 +578,8 @@ class AuthBackend(object):
|
|||||||
if user.check_password(password):
|
if user.check_password(password):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
########## Register related
|
|
||||||
|
# Register related
|
||||||
class RegistrationBackend(object):
|
class RegistrationBackend(object):
|
||||||
"""
|
"""
|
||||||
A registration backend which follows a simple workflow:
|
A registration backend which follows a simple workflow:
|
||||||
@ -755,9 +754,9 @@ class RegistrationForm(forms.Form):
|
|||||||
"""
|
"""
|
||||||
attrs_dict = {'class': 'input'}
|
attrs_dict = {'class': 'input'}
|
||||||
|
|
||||||
email = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict,
|
email = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=75)),
|
||||||
maxlength=75)),
|
|
||||||
label=_("Email address"))
|
label=_("Email address"))
|
||||||
|
|
||||||
userid = forms.RegexField(regex=r'^\w+$',
|
userid = forms.RegexField(regex=r'^\w+$',
|
||||||
max_length=40,
|
max_length=40,
|
||||||
required=False,
|
required=False,
|
||||||
@ -825,12 +824,13 @@ class RegistrationForm(forms.Form):
|
|||||||
raise forms.ValidationError(_("The two password fields didn't match."))
|
raise forms.ValidationError(_("The two password fields didn't match."))
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class DetailedRegistrationForm(RegistrationForm):
|
class DetailedRegistrationForm(RegistrationForm):
|
||||||
attrs_dict = {'class': 'input'}
|
attrs_dict = {'class': 'input'}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from seahub.settings import REGISTRATION_DETAILS_MAP
|
from seahub.settings import REGISTRATION_DETAILS_MAP
|
||||||
except:
|
except ImportError:
|
||||||
REGISTRATION_DETAILS_MAP = None
|
REGISTRATION_DETAILS_MAP = None
|
||||||
|
|
||||||
if REGISTRATION_DETAILS_MAP:
|
if REGISTRATION_DETAILS_MAP:
|
||||||
|
@ -133,6 +133,7 @@ def base(request):
|
|||||||
'constance_enabled': dj_settings.CONSTANCE_ENABLED,
|
'constance_enabled': dj_settings.CONSTANCE_ENABLED,
|
||||||
'FILE_SERVER_ROOT': file_server_root,
|
'FILE_SERVER_ROOT': file_server_root,
|
||||||
'LOGIN_URL': dj_settings.LOGIN_URL,
|
'LOGIN_URL': dj_settings.LOGIN_URL,
|
||||||
|
'LOGOUT_URL': dj_settings.LOGOUT_URL,
|
||||||
'enable_thumbnail': ENABLE_THUMBNAIL,
|
'enable_thumbnail': ENABLE_THUMBNAIL,
|
||||||
'thumbnail_size_for_original': THUMBNAIL_SIZE_FOR_ORIGINAL,
|
'thumbnail_size_for_original': THUMBNAIL_SIZE_FOR_ORIGINAL,
|
||||||
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
|
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
@ -10,11 +9,12 @@ from seahub.base.fields import LowerCaseCharField
|
|||||||
from seahub.invitations.settings import INVITATIONS_TOKEN_AGE
|
from seahub.invitations.settings import INVITATIONS_TOKEN_AGE
|
||||||
from seahub.utils import gen_token, get_site_name
|
from seahub.utils import gen_token, get_site_name
|
||||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||||
from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
|
from seahub.utils.mail import send_html_email_with_dj_template
|
||||||
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
|
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
|
||||||
|
|
||||||
GUEST = 'Guest'
|
GUEST = 'Guest'
|
||||||
|
|
||||||
|
|
||||||
class InvitationManager(models.Manager):
|
class InvitationManager(models.Manager):
|
||||||
def add(self, inviter, accepter, invite_type=GUEST):
|
def add(self, inviter, accepter, invite_type=GUEST):
|
||||||
token = gen_token(max_length=32)
|
token = gen_token(max_length=32)
|
||||||
@ -26,8 +26,7 @@ class InvitationManager(models.Manager):
|
|||||||
return i
|
return i
|
||||||
|
|
||||||
def get_by_inviter(self, inviter):
|
def get_by_inviter(self, inviter):
|
||||||
return super(InvitationManager,
|
return super(InvitationManager, self).filter(inviter=inviter).order_by('-invite_time')
|
||||||
self).filter(inviter=inviter).order_by('-invite_time')
|
|
||||||
|
|
||||||
def delete_all_expire_invitation(self):
|
def delete_all_expire_invitation(self):
|
||||||
super(InvitationManager, self).filter(expire_time__lte=timezone.now(), accept_time__isnull=True).delete()
|
super(InvitationManager, self).filter(expire_time__lte=timezone.now(), accept_time__isnull=True).delete()
|
||||||
@ -93,16 +92,13 @@ class Invitation(models.Model):
|
|||||||
context = self.to_dict()
|
context = self.to_dict()
|
||||||
context['site_name'] = get_site_name()
|
context['site_name'] = get_site_name()
|
||||||
|
|
||||||
# subject = render_to_string('invitations/invitation_email_subject.txt',
|
|
||||||
# context).rstrip()
|
|
||||||
subject = _('%(user)s invited you to join %(site_name)s.') % {
|
subject = _('%(user)s invited you to join %(site_name)s.') % {
|
||||||
'user': self.inviter, 'site_name': get_site_name()}
|
'user': self.inviter, 'site_name': get_site_name()}
|
||||||
return send_html_email_with_dj_template(
|
|
||||||
email, dj_template='invitations/invitation_email.html',
|
return send_html_email_with_dj_template(email,
|
||||||
context=context,
|
|
||||||
subject=subject,
|
subject=subject,
|
||||||
priority=MAIL_PRIORITY.now
|
dj_template='invitations/invitation_email.html',
|
||||||
)
|
context=context)
|
||||||
|
|
||||||
|
|
||||||
class RepoShareInvitationManager(models.Manager):
|
class RepoShareInvitationManager(models.Manager):
|
||||||
@ -136,7 +132,9 @@ class RepoShareInvitationManager(models.Manager):
|
|||||||
def list_by_invitation(self, invitation):
|
def list_by_invitation(self, invitation):
|
||||||
return self.select_related('invitation').filter(invitation=invitation)
|
return self.select_related('invitation').filter(invitation=invitation)
|
||||||
|
|
||||||
|
|
||||||
class RepoShareInvitation(models.Model):
|
class RepoShareInvitation(models.Model):
|
||||||
|
|
||||||
PERMISSION_CHOICES = (
|
PERMISSION_CHOICES = (
|
||||||
(PERMISSION_READ, 'read only'),
|
(PERMISSION_READ, 'read only'),
|
||||||
(PERMISSION_READ_WRITE, 'read and write')
|
(PERMISSION_READ_WRITE, 'read and write')
|
||||||
|
@ -12,13 +12,15 @@ import seaserv
|
|||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
|
|
||||||
from seahub.profile.models import Profile
|
from seahub.profile.models import Profile
|
||||||
from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
|
from seahub.utils.mail import send_html_email_with_dj_template
|
||||||
from seahub.utils import get_site_name
|
from seahub.utils import get_site_name
|
||||||
|
|
||||||
# Get an instance of a logger
|
# Get an instance of a logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
help = 'Send Email notifications to admins if there are virus files detected .'
|
help = 'Send Email notifications to admins if there are virus files detected .'
|
||||||
label = "notifications_notify_admins_on_virus"
|
label = "notifications_notify_admins_on_virus"
|
||||||
|
|
||||||
@ -52,11 +54,9 @@ class Command(BaseCommand):
|
|||||||
user_language = self.get_user_language(u.email)
|
user_language = self.get_user_language(u.email)
|
||||||
translation.activate(user_language)
|
translation.activate(user_language)
|
||||||
|
|
||||||
send_html_email_with_dj_template(
|
send_html_email_with_dj_template(u.email,
|
||||||
u.email, dj_template='notifications/notify_virus.html',
|
|
||||||
subject=_('Virus detected on %s') % get_site_name(),
|
subject=_('Virus detected on %s') % get_site_name(),
|
||||||
priority=MAIL_PRIORITY.now
|
dj_template='notifications/notify_virus.html')
|
||||||
)
|
|
||||||
|
|
||||||
# restore current language
|
# restore current language
|
||||||
translation.activate(cur_language)
|
translation.activate(cur_language)
|
||||||
@ -68,11 +68,9 @@ class Command(BaseCommand):
|
|||||||
return
|
return
|
||||||
|
|
||||||
for mail in notify_list:
|
for mail in notify_list:
|
||||||
send_html_email_with_dj_template(
|
send_html_email_with_dj_template(mail,
|
||||||
mail, dj_template='notifications/notify_virus.html',
|
|
||||||
subject=_('Virus detected on %s') % get_site_name(),
|
subject=_('Virus detected on %s') % get_site_name(),
|
||||||
priority=MAIL_PRIORITY.now
|
dj_template='notifications/notify_virus.html')
|
||||||
)
|
|
||||||
|
|
||||||
def email_repo_owner(self, repo_file):
|
def email_repo_owner(self, repo_file):
|
||||||
repo_id, file_path = repo_file.split(':', 1)
|
repo_id, file_path = repo_file.split(':', 1)
|
||||||
@ -88,16 +86,12 @@ class Command(BaseCommand):
|
|||||||
translation.activate(user_language)
|
translation.activate(user_language)
|
||||||
|
|
||||||
contact_email = Profile.objects.get_contact_email_by_user(owner)
|
contact_email = Profile.objects.get_contact_email_by_user(owner)
|
||||||
send_html_email_with_dj_template(
|
send_html_email_with_dj_template(contact_email,
|
||||||
contact_email, dj_template='notifications/notify_virus.html',
|
|
||||||
context={'owner': owner,
|
|
||||||
'file_url': reverse('view_lib_file',
|
|
||||||
args=[repo_id, file_path]),
|
|
||||||
'file_name': os.path.basename(file_path),
|
|
||||||
},
|
|
||||||
subject=_('Virus detected on %s') % get_site_name(),
|
subject=_('Virus detected on %s') % get_site_name(),
|
||||||
priority=MAIL_PRIORITY.now
|
dj_template='notifications/notify_virus.html',
|
||||||
)
|
context={'owner': owner,
|
||||||
|
'file_url': reverse('view_lib_file', args=[repo_id, file_path]),
|
||||||
|
'file_name': os.path.basename(file_path)})
|
||||||
|
|
||||||
# restore current language
|
# restore current language
|
||||||
translation.activate(cur_language)
|
translation.activate(cur_language)
|
||||||
|
@ -11,3 +11,5 @@ ONLYOFFICE_JWT_SECRET = getattr(settings, 'ONLYOFFICE_JWT_SECRET', '')
|
|||||||
|
|
||||||
# if True, file will be saved when user click save btn on file editing page
|
# if True, file will be saved when user click save btn on file editing page
|
||||||
ONLYOFFICE_FORCE_SAVE = getattr(settings, 'ONLYOFFICE_FORCE_SAVE', False)
|
ONLYOFFICE_FORCE_SAVE = getattr(settings, 'ONLYOFFICE_FORCE_SAVE', False)
|
||||||
|
|
||||||
|
ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN = getattr(settings, 'ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN', False)
|
||||||
|
@ -218,7 +218,6 @@ INSTALLED_APPS = [
|
|||||||
'statici18n',
|
'statici18n',
|
||||||
'constance',
|
'constance',
|
||||||
'constance.backends.database',
|
'constance.backends.database',
|
||||||
'post_office',
|
|
||||||
'termsandconditions',
|
'termsandconditions',
|
||||||
'webpack_loader',
|
'webpack_loader',
|
||||||
|
|
||||||
|
@ -3,34 +3,30 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from dateutil.relativedelta import relativedelta
|
|
||||||
from constance import config
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import HttpResponse, HttpResponseRedirect, Http404, \
|
from django.http import HttpResponse, HttpResponseRedirect, Http404, \
|
||||||
HttpResponseBadRequest
|
HttpResponseBadRequest
|
||||||
from django.utils.translation import ugettext as _, activate
|
from django.utils.translation import ugettext as _, activate
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
|
||||||
import seaserv
|
import seaserv
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
from seaserv import ccnet_threaded_rpc
|
|
||||||
from pysearpc import SearpcError
|
from pysearpc import SearpcError
|
||||||
|
|
||||||
from seahub.share.forms import FileLinkShareForm, \
|
from seahub.share.forms import FileLinkShareForm, \
|
||||||
UploadLinkShareForm
|
UploadLinkShareForm
|
||||||
from seahub.share.models import FileShare, UploadLinkShare, OrgFileShare
|
from seahub.share.models import FileShare, UploadLinkShare
|
||||||
from seahub.share.signals import share_repo_to_user_successful
|
from seahub.share.signals import share_repo_to_user_successful
|
||||||
from seahub.auth.decorators import login_required, login_required_ajax
|
from seahub.auth.decorators import login_required, login_required_ajax
|
||||||
from seahub.base.decorators import require_POST
|
from seahub.base.decorators import require_POST
|
||||||
from seahub.contacts.signals import mail_sended
|
from seahub.contacts.signals import mail_sended
|
||||||
from seahub.views import is_registered_user, check_folder_permission
|
from seahub.views import is_registered_user, check_folder_permission
|
||||||
from seahub.utils import string2list, gen_shared_link, \
|
from seahub.utils import string2list, IS_EMAIL_CONFIGURED, check_filename_with_rename, \
|
||||||
gen_shared_upload_link, IS_EMAIL_CONFIGURED, check_filename_with_rename, \
|
|
||||||
is_valid_username, is_valid_email, send_html_email, is_org_context, \
|
is_valid_username, is_valid_email, send_html_email, is_org_context, \
|
||||||
gen_token, normalize_cache_key, get_site_name
|
gen_token, normalize_cache_key, get_site_name
|
||||||
from seahub.utils.mail import send_html_email_with_dj_template, MAIL_PRIORITY
|
from seahub.utils.mail import send_html_email_with_dj_template
|
||||||
from seahub.settings import SITE_ROOT, REPLACE_FROM_EMAIL, \
|
from seahub.settings import SITE_ROOT, REPLACE_FROM_EMAIL, \
|
||||||
ADD_REPLY_TO_HEADER, SHARE_LINK_EMAIL_LANGUAGE, \
|
ADD_REPLY_TO_HEADER, SHARE_LINK_EMAIL_LANGUAGE, \
|
||||||
SHARE_LINK_AUDIT_CODE_TIMEOUT
|
SHARE_LINK_AUDIT_CODE_TIMEOUT
|
||||||
@ -39,21 +35,25 @@ from seahub.profile.models import Profile
|
|||||||
# Get an instance of a logger
|
# Get an instance of a logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
########## rpc wrapper
|
|
||||||
|
# rpc wrapper
|
||||||
def is_org_repo_owner(username, repo_id):
|
def is_org_repo_owner(username, repo_id):
|
||||||
owner = seaserv.seafserv_threaded_rpc.get_org_repo_owner(repo_id)
|
owner = seaserv.seafserv_threaded_rpc.get_org_repo_owner(repo_id)
|
||||||
return True if owner == username else False
|
return True if owner == username else False
|
||||||
|
|
||||||
|
|
||||||
def org_share_repo(org_id, repo_id, from_user, to_user, permission):
|
def org_share_repo(org_id, repo_id, from_user, to_user, permission):
|
||||||
return seaserv.seafserv_threaded_rpc.org_add_share(org_id, repo_id,
|
return seaserv.seafserv_threaded_rpc.org_add_share(org_id, repo_id,
|
||||||
from_user, to_user,
|
from_user, to_user,
|
||||||
permission)
|
permission)
|
||||||
|
|
||||||
|
|
||||||
def org_remove_share(org_id, repo_id, from_user, to_user):
|
def org_remove_share(org_id, repo_id, from_user, to_user):
|
||||||
return seaserv.seafserv_threaded_rpc.org_remove_share(org_id, repo_id,
|
return seaserv.seafserv_threaded_rpc.org_remove_share(org_id, repo_id,
|
||||||
from_user, to_user)
|
from_user, to_user)
|
||||||
|
|
||||||
########## functions
|
|
||||||
|
# functions
|
||||||
def share_to_group(request, repo, group, permission):
|
def share_to_group(request, repo, group, permission):
|
||||||
"""Share repo to group with given permission.
|
"""Share repo to group with given permission.
|
||||||
"""
|
"""
|
||||||
@ -83,6 +83,7 @@ def share_to_group(request, repo, group, permission):
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def share_to_user(request, repo, to_user, permission):
|
def share_to_user(request, repo, to_user, permission):
|
||||||
"""Share repo to a user with given permission.
|
"""Share repo to a user with given permission.
|
||||||
"""
|
"""
|
||||||
@ -109,8 +110,8 @@ def share_to_user(request, repo, to_user, permission):
|
|||||||
else:
|
else:
|
||||||
seafile_api.share_repo(repo_id, from_user, to_user, permission)
|
seafile_api.share_repo(repo_id, from_user, to_user, permission)
|
||||||
except SearpcError as e:
|
except SearpcError as e:
|
||||||
return False
|
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
# send a signal when sharing repo successful
|
# send a signal when sharing repo successful
|
||||||
share_repo_to_user_successful.send(sender=None,
|
share_repo_to_user_successful.send(sender=None,
|
||||||
@ -119,7 +120,8 @@ def share_to_user(request, repo, to_user, permission):
|
|||||||
path='/', org_id=org_id)
|
path='/', org_id=org_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
########## share link
|
|
||||||
|
# share link
|
||||||
@login_required_ajax
|
@login_required_ajax
|
||||||
def send_shared_link(request):
|
def send_shared_link(request):
|
||||||
"""
|
"""
|
||||||
@ -131,7 +133,8 @@ def send_shared_link(request):
|
|||||||
content_type = 'application/json; charset=utf-8'
|
content_type = 'application/json; charset=utf-8'
|
||||||
|
|
||||||
if not IS_EMAIL_CONFIGURED:
|
if not IS_EMAIL_CONFIGURED:
|
||||||
data = json.dumps({'error':_('Sending shared link failed. Email service is not properly configured, please contact administrator.')})
|
data = json.dumps({'error': _('Sending shared link failed. \
|
||||||
|
Email service is not properly configured, please contact administrator.')})
|
||||||
return HttpResponse(data, status=500, content_type=content_type)
|
return HttpResponse(data, status=500, content_type=content_type)
|
||||||
|
|
||||||
form = FileLinkShareForm(request.POST)
|
form = FileLinkShareForm(request.POST)
|
||||||
@ -207,6 +210,7 @@ def send_shared_link(request):
|
|||||||
return HttpResponseBadRequest(json.dumps(form.errors),
|
return HttpResponseBadRequest(json.dumps(form.errors),
|
||||||
content_type=content_type)
|
content_type=content_type)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def save_shared_link(request):
|
def save_shared_link(request):
|
||||||
"""Save public share link to one's library.
|
"""Save public share link to one's library.
|
||||||
@ -246,6 +250,7 @@ def save_shared_link(request):
|
|||||||
messages.success(request, _('Successfully saved.'))
|
messages.success(request, _('Successfully saved.'))
|
||||||
return HttpResponseRedirect(next_page)
|
return HttpResponseRedirect(next_page)
|
||||||
|
|
||||||
|
|
||||||
@login_required_ajax
|
@login_required_ajax
|
||||||
def send_shared_upload_link(request):
|
def send_shared_upload_link(request):
|
||||||
"""
|
"""
|
||||||
@ -257,7 +262,8 @@ def send_shared_upload_link(request):
|
|||||||
content_type = 'application/json; charset=utf-8'
|
content_type = 'application/json; charset=utf-8'
|
||||||
|
|
||||||
if not IS_EMAIL_CONFIGURED:
|
if not IS_EMAIL_CONFIGURED:
|
||||||
data = json.dumps({'error':_('Sending shared upload link failed. Email service is not properly configured, please contact administrator.')})
|
data = json.dumps({'error': _('Sending shared upload link failed. \
|
||||||
|
Email service is not properly configured, please contact administrator.')})
|
||||||
return HttpResponse(data, status=500, content_type=content_type)
|
return HttpResponse(data, status=500, content_type=content_type)
|
||||||
|
|
||||||
form = UploadLinkShareForm(request.POST)
|
form = UploadLinkShareForm(request.POST)
|
||||||
@ -317,6 +323,7 @@ def send_shared_upload_link(request):
|
|||||||
return HttpResponseBadRequest(json.dumps(form.errors),
|
return HttpResponseBadRequest(json.dumps(form.errors),
|
||||||
content_type=content_type)
|
content_type=content_type)
|
||||||
|
|
||||||
|
|
||||||
@login_required_ajax
|
@login_required_ajax
|
||||||
@require_POST
|
@require_POST
|
||||||
def ajax_private_share_dir(request):
|
def ajax_private_share_dir(request):
|
||||||
@ -359,8 +366,7 @@ def ajax_private_share_dir(request):
|
|||||||
sub_repo_id = seaserv.seafserv_threaded_rpc.create_org_virtual_repo(
|
sub_repo_id = seaserv.seafserv_threaded_rpc.create_org_virtual_repo(
|
||||||
org_id, repo_id, path, name, name, username)
|
org_id, repo_id, path, name, name, username)
|
||||||
else:
|
else:
|
||||||
sub_repo_id = seafile_api.create_virtual_repo(repo_id, path,
|
sub_repo_id = seafile_api.create_virtual_repo(repo_id, path, name, name, username)
|
||||||
name, name, username)
|
|
||||||
sub_repo = seafile_api.get_repo(sub_repo_id)
|
sub_repo = seafile_api.get_repo(sub_repo_id)
|
||||||
except SearpcError as e:
|
except SearpcError as e:
|
||||||
result['error'] = e.msg
|
result['error'] = e.msg
|
||||||
@ -423,6 +429,7 @@ def ajax_private_share_dir(request):
|
|||||||
data = json.dumps({"error": _("Please check the email(s) you entered")})
|
data = json.dumps({"error": _("Please check the email(s) you entered")})
|
||||||
return HttpResponse(data, status=400, content_type=content_type)
|
return HttpResponse(data, status=400, content_type=content_type)
|
||||||
|
|
||||||
|
|
||||||
def ajax_get_link_audit_code(request):
|
def ajax_get_link_audit_code(request):
|
||||||
"""
|
"""
|
||||||
Generate a token, and record that token with email in cache, expires in
|
Generate a token, and record that token with email in cache, expires in
|
||||||
@ -455,19 +462,18 @@ def ajax_get_link_audit_code(request):
|
|||||||
|
|
||||||
# send code to user via email
|
# send code to user via email
|
||||||
subject = _("Verification code for visiting share links")
|
subject = _("Verification code for visiting share links")
|
||||||
c = {
|
c = {'code': code}
|
||||||
'code': code,
|
|
||||||
}
|
send_success = send_html_email_with_dj_template(email,
|
||||||
try:
|
subject=subject,
|
||||||
send_html_email_with_dj_template(
|
dj_template='share/audit_code_email.html',
|
||||||
email, dj_template='share/audit_code_email.html',
|
context=c)
|
||||||
context=c, subject=subject, priority=MAIL_PRIORITY.now
|
|
||||||
)
|
if not send_success:
|
||||||
return HttpResponse(json.dumps({'success': True}), status=200,
|
|
||||||
content_type=content_type)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error('Failed to send audit code via email to %s')
|
logger.error('Failed to send audit code via email to %s')
|
||||||
logger.error(e)
|
|
||||||
return HttpResponse(json.dumps({
|
return HttpResponse(json.dumps({
|
||||||
"error": _("Failed to send a verification code, please try again later.")
|
"error": _("Failed to send a verification code, please try again later.")
|
||||||
}), status=500, content_type=content_type)
|
}), status=500, content_type=content_type)
|
||||||
|
|
||||||
|
return HttpResponse(json.dumps({'success': True}), status=200,
|
||||||
|
content_type=content_type)
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
siteName: '{{ site_name|escapejs }}',
|
siteName: '{{ site_name|escapejs }}',
|
||||||
siteRoot: '{{ SITE_ROOT }}',
|
siteRoot: '{{ SITE_ROOT }}',
|
||||||
loginUrl: '{{ LOGIN_URL }}',
|
loginUrl: '{{ LOGIN_URL }}',
|
||||||
|
logoutUrl: '{{ LOGOUT_URL }}',
|
||||||
isPro: '{{ is_pro }}',
|
isPro: '{{ is_pro }}',
|
||||||
isDocs: '{{ is_docs }}',
|
isDocs: '{{ is_docs }}',
|
||||||
lang: '{{ LANGUAGE_CODE }}',
|
lang: '{{ LANGUAGE_CODE }}',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% extends "base_for_react.html" %}
|
{% extends "base_for_react.html" %}
|
||||||
{% load render_bundle from webpack_loader %}
|
{% load render_bundle from webpack_loader %}
|
||||||
|
{% load seahub_tags %}
|
||||||
|
|
||||||
{% block extra_style %}
|
{% block extra_style %}
|
||||||
{% render_bundle 'app' 'css' %}
|
{% render_bundle 'app' 'css' %}
|
||||||
@ -16,6 +17,15 @@
|
|||||||
uploadLinkExpireDaysMin: {{ upload_link_expire_days_min }},
|
uploadLinkExpireDaysMin: {{ upload_link_expire_days_min }},
|
||||||
uploadLinkExpireDaysMax: {{ upload_link_expire_days_max }}
|
uploadLinkExpireDaysMax: {{ upload_link_expire_days_max }}
|
||||||
});
|
});
|
||||||
|
{% if onlyoffice_desktop_editors_portal_login %}
|
||||||
|
let params = {
|
||||||
|
"displayName": "{{request.user.username|email2nickname|escapejs}}",
|
||||||
|
"email": "{{request.user.username|escapejs}}",
|
||||||
|
"domain": "{{service_url}}",
|
||||||
|
"provider": "{{site_name}}"
|
||||||
|
}
|
||||||
|
window.AscDesktopEditor.execCommand('portal:login', JSON.stringify(params));
|
||||||
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
{% render_bundle 'app' 'js' %}
|
{% render_bundle 'app' 'js' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,27 +1,23 @@
|
|||||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
import os
|
import os
|
||||||
from django.template import Context, loader
|
import logging
|
||||||
from post_office import mail
|
from django.template import loader
|
||||||
from post_office.models import PRIORITY
|
from django.core.mail import EmailMessage
|
||||||
from constance import config
|
|
||||||
|
|
||||||
from seahub.utils import get_site_scheme_and_netloc, get_site_name
|
from seahub.utils import get_site_scheme_and_netloc, get_site_name
|
||||||
from seahub.settings import MEDIA_URL, LOGO_PATH, \
|
from seahub.settings import MEDIA_URL, LOGO_PATH, \
|
||||||
MEDIA_ROOT, CUSTOM_LOGO_PATH
|
MEDIA_ROOT, CUSTOM_LOGO_PATH
|
||||||
|
|
||||||
MAIL_PRIORITY = PRIORITY # 'low medium high now'
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def send_html_email_with_dj_template(recipients, subject, dj_template,
|
|
||||||
context={}, sender=None, template=None,
|
def send_html_email_with_dj_template(recipients, subject, dj_template, context={}):
|
||||||
message='', headers=None,
|
|
||||||
priority=None, backend=''):
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- `recipients`:
|
- `recipients`:
|
||||||
- `subject`:
|
- `subject`:
|
||||||
- `sender`:
|
- `dj_template`:
|
||||||
- `template`:
|
|
||||||
- `context`:
|
- `context`:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -42,7 +38,12 @@ def send_html_email_with_dj_template(recipients, subject, dj_template,
|
|||||||
t = loader.get_template(dj_template)
|
t = loader.get_template(dj_template)
|
||||||
html_message = t.render(context)
|
html_message = t.render(context)
|
||||||
|
|
||||||
return mail.send(recipients, sender=sender, template=template, context=context,
|
mail = EmailMessage(subject=subject, body=html_message, to=[recipients])
|
||||||
subject=subject, message=message,
|
mail.content_subtype = "html"
|
||||||
html_message=html_message, headers=headers, priority=priority,
|
|
||||||
backend=backend)
|
try:
|
||||||
|
mail.send()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
return False
|
||||||
|
@ -60,7 +60,7 @@ from seahub.settings import AVATAR_FILE_STORAGE, \
|
|||||||
DTABLE_WEB_SERVER
|
DTABLE_WEB_SERVER
|
||||||
|
|
||||||
from seahub.wopi.settings import ENABLE_OFFICE_WEB_APP
|
from seahub.wopi.settings import ENABLE_OFFICE_WEB_APP
|
||||||
from seahub.onlyoffice.settings import ENABLE_ONLYOFFICE
|
from seahub.onlyoffice.settings import ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN
|
||||||
from seahub.ocm.settings import ENABLE_OCM, OCM_REMOTE_SERVERS
|
from seahub.ocm.settings import ENABLE_OCM, OCM_REMOTE_SERVERS
|
||||||
from seahub.constants import HASH_URLS, PERMISSION_READ
|
from seahub.constants import HASH_URLS, PERMISSION_READ
|
||||||
|
|
||||||
@ -1180,6 +1180,7 @@ def react_fake_view(request, **kwargs):
|
|||||||
max_upload_file_size = -1
|
max_upload_file_size = -1
|
||||||
|
|
||||||
return render(request, "react_app.html", {
|
return render(request, "react_app.html", {
|
||||||
|
"onlyoffice_desktop_editors_portal_login": ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN,
|
||||||
"guide_enabled": guide_enabled,
|
"guide_enabled": guide_enabled,
|
||||||
'trash_repos_expire_days': expire_days if expire_days > 0 else 30,
|
'trash_repos_expire_days': expire_days if expire_days > 0 else 30,
|
||||||
'dtable_web_server': DTABLE_WEB_SERVER,
|
'dtable_web_server': DTABLE_WEB_SERVER,
|
||||||
|
@ -57,7 +57,6 @@ from seahub.utils.ldap import get_ldap_info
|
|||||||
from seahub.utils.licenseparse import parse_license, user_number_over_limit
|
from seahub.utils.licenseparse import parse_license, user_number_over_limit
|
||||||
from seahub.utils.rpc import mute_seafile_api
|
from seahub.utils.rpc import mute_seafile_api
|
||||||
from seahub.utils.sysinfo import get_platform_name
|
from seahub.utils.sysinfo import get_platform_name
|
||||||
from seahub.utils.mail import send_html_email_with_dj_template
|
|
||||||
from seahub.utils.ms_excel import write_xls
|
from seahub.utils.ms_excel import write_xls
|
||||||
from seahub.utils.user_permissions import get_basic_user_roles, \
|
from seahub.utils.user_permissions import get_basic_user_roles, \
|
||||||
get_user_role, get_basic_admin_roles
|
get_user_role, get_basic_admin_roles
|
||||||
|
Loading…
Reference in New Issue
Block a user