mirror of
https://github.com/haiwen/seahub.git
synced 2025-04-27 11:01:14 +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-select": "^2.4.1",
|
||||
"reactstrap": "^6.4.0",
|
||||
"seafile-js": "0.2.163",
|
||||
"seafile-js": "0.2.164",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"unified": "^7.0.0",
|
||||
"url-parse": "^1.4.3",
|
||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Utils } from '../../utils/utils';
|
||||
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';
|
||||
|
||||
const propTypes = {
|
||||
@ -164,7 +164,7 @@ class Account extends Component {
|
||||
</div>
|
||||
<a href={siteRoot + 'profile/'} className="item">{gettext('Settings')}</a>
|
||||
{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>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { gettext, logoutUrl } from '../../utils/constants';
|
||||
|
||||
export default function Logout() {
|
||||
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>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class AboutDialog extends React.Component {
|
||||
<button type="button" className="close" onClick={this.toggle}><span aria-hidden="true">×</span></button>
|
||||
<div className="about-content">
|
||||
<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><a href={href} target="_blank">{gettext('About Us')}</a></p>
|
||||
</div>
|
||||
|
@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
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 { 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 Loading from '../loading';
|
||||
|
||||
import '../../css/manage-members-dialog.css';
|
||||
|
||||
const propTypes = {
|
||||
@ -21,13 +23,45 @@ class ManageMembersDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true, // first loading
|
||||
isLoadingMore: false,
|
||||
groupMembers: [],
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
hasNextPage: false,
|
||||
selectedOption: null,
|
||||
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) => {
|
||||
this.setState({
|
||||
selectedOption: option,
|
||||
@ -41,8 +75,9 @@ class ManageMembersDialog extends React.Component {
|
||||
emails.push(this.state.selectedOption[i].email);
|
||||
}
|
||||
seafileAPI.addGroupMembers(this.props.groupID, emails).then((res) => {
|
||||
this.onGroupMembersChange();
|
||||
const newMembers = res.data.success;
|
||||
this.setState({
|
||||
groupMembers: [].concat(newMembers, this.state.groupMembers),
|
||||
selectedOption: null,
|
||||
});
|
||||
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) => {
|
||||
this.setState({
|
||||
isItemFreezed: isFreezed
|
||||
@ -82,11 +102,43 @@ class ManageMembersDialog extends React.Component {
|
||||
this.props.toggleManageMembersDialog();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.listGroupMembers();
|
||||
handleScroll = (event) => {
|
||||
// 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() {
|
||||
const { isLoading, hasNextPage } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Manage group members')}</ModalHeader>
|
||||
@ -113,36 +165,41 @@ class ManageMembersDialog extends React.Component {
|
||||
);
|
||||
})
|
||||
}
|
||||
<div className="manage-members">
|
||||
<Table size="sm" className="manage-members-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%"></th>
|
||||
<th width="45%">{gettext('Name')}</th>
|
||||
<th width="30%">{gettext('Role')}</th>
|
||||
<th width="10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.state.groupMembers.length > 0 &&
|
||||
this.state.groupMembers.map((item, index = 0) => {
|
||||
<div className="manage-members" onScroll={this.handleScroll}>
|
||||
{isLoading ? <Loading /> : (
|
||||
<Fragment>
|
||||
<Table size="sm" className="manage-members-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%"></th>
|
||||
<th width="45%">{gettext('Name')}</th>
|
||||
<th width="30%">{gettext('Role')}</th>
|
||||
<th width="10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.state.groupMembers.length > 0 &&
|
||||
this.state.groupMembers.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<Member
|
||||
key={index}
|
||||
item={item}
|
||||
onGroupMembersChange={this.onGroupMembersChange}
|
||||
changeMember={this.changeMember}
|
||||
deleteMember={this.deleteMember}
|
||||
groupID={this.props.groupID}
|
||||
isOwner={this.props.isOwner}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
toggleItemFreezed={this.toggleItemFreezed}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</Table>
|
||||
}
|
||||
</tbody>
|
||||
</Table>
|
||||
{hasNextPage && <Loading />}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
@ -157,7 +214,8 @@ ManageMembersDialog.propTypes = propTypes;
|
||||
|
||||
const MemberPropTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
onGroupMembersChange: PropTypes.func.isRequired,
|
||||
changeMember: PropTypes.func.isRequired,
|
||||
deleteMember: PropTypes.func.isRequired,
|
||||
groupID: PropTypes.string.isRequired,
|
||||
isOwner: PropTypes.bool.isRequired,
|
||||
};
|
||||
@ -175,16 +233,18 @@ class Member extends React.PureComponent {
|
||||
onChangeUserRole = (role) => {
|
||||
let isAdmin = role === 'Admin' ? 'True' : 'False';
|
||||
seafileAPI.setGroupAdmin(this.props.groupID, this.props.item.email, isAdmin).then((res) => {
|
||||
this.props.onGroupMembersChange();
|
||||
});
|
||||
this.setState({
|
||||
highlight: false,
|
||||
this.props.changeMember(res.data);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
deleteMember = (name) => {
|
||||
seafileAPI.deleteGroupMember(this.props.groupID, name).then((res) => {
|
||||
this.props.onGroupMembersChange();
|
||||
deleteMember = () => {
|
||||
const { item } = this.props;
|
||||
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 => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
@ -243,8 +303,7 @@ class Member extends React.PureComponent {
|
||||
{(deleteAuthority && !this.props.isItemFreezed) &&
|
||||
<i
|
||||
className="fa fa-times delete-group-member-icon"
|
||||
name={item.email}
|
||||
onClick={this.deleteMember.bind(this, item.email)}>
|
||||
onClick={this.deleteMember}>
|
||||
</i>
|
||||
}
|
||||
</td>
|
||||
|
@ -171,8 +171,10 @@ class DirentListItem extends React.Component {
|
||||
|
||||
onItemClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dirent = this.props.dirent;
|
||||
if (this.state.isRenameing) {
|
||||
return;
|
||||
}
|
||||
if (Utils.imageCheck(dirent.name)) {
|
||||
this.props.showImagePopup(dirent);
|
||||
} else {
|
||||
@ -664,19 +666,19 @@ class DirentListItem extends React.Component {
|
||||
);
|
||||
const mobileItem = (
|
||||
<tr>
|
||||
<td>
|
||||
<td onClick={this.onItemClick}>
|
||||
<div className="dir-icon">
|
||||
{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="" />
|
||||
}
|
||||
{dirent.is_locked && <img className="locked" src={mediaUrl + 'img/file-locked-32.png'} alt={gettext('locked')} title={lockedInfo}/>}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<td onClick={this.onItemClick}>
|
||||
{this.state.isRenameing ?
|
||||
<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 />
|
||||
{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 folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(true);
|
||||
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name, 192);
|
||||
|
||||
if (item.thumbnail_url !== '') {
|
||||
fileIconUrl = item.thumbnail_url;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="search-result-item" onClick={this.onClickHandler}>
|
||||
<img className={className} src={fileIconUrl} alt="" />
|
||||
|
@ -178,6 +178,7 @@ class Search extends Component {
|
||||
items[i]['is_dir'] = data[i].is_dir;
|
||||
items[i]['link_content'] = decodeURI(data[i].fullpath).substring(1);
|
||||
items[i]['content'] = data[i].content_highlight;
|
||||
items[i]['thumbnail_url'] = data[i].thumbnail_url;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||
import { Link } from '@reach/router';
|
||||
import { Link, navigate } from '@reach/router';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext, siteRoot, isPro, username, folderPermEnabled, isSystemStaff, enableResetEncryptedRepoPassword, isEmailConfigured } from '../../utils/constants';
|
||||
import ModalPortal from '../../components/modal-portal';
|
||||
@ -479,14 +479,21 @@ class SharedRepoListItem extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
visitRepo = () => {
|
||||
if (!this.state.isRenaming) {
|
||||
navigate(this.repoURL);
|
||||
}
|
||||
}
|
||||
|
||||
renderMobileUI = () => {
|
||||
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
|
||||
let { repo } = this.props;
|
||||
this.repoURL = libPath;
|
||||
return (
|
||||
<Fragment>
|
||||
<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>
|
||||
<td onClick={this.visitRepo}><img src={iconUrl} title={iconTitle} width="24" alt={iconTitle}/></td>
|
||||
<td onClick={this.visitRepo}>
|
||||
{this.state.isRenaming ?
|
||||
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
||||
<Link to={libPath}>{repo.repo_name}</Link>
|
||||
|
@ -6,6 +6,7 @@ class SysAdminAdminUser {
|
||||
this.contact_email = object.contact_email;
|
||||
this.login_id = object.login_id;
|
||||
this.last_login = object.last_login;
|
||||
this.last_access_time = object.last_access_time;
|
||||
this.create_time = object.create_time;
|
||||
this.is_active = object.is_active;
|
||||
this.is_staff = object.is_staff;
|
||||
|
@ -5,6 +5,7 @@ class SysAdminUser {
|
||||
this.contact_email = object.contact_email;
|
||||
this.login_id = object.login_id;
|
||||
this.last_login = object.last_login;
|
||||
this.last_access_time = object.last_access_time;
|
||||
this.create_time = object.create_time;
|
||||
this.is_active = object.is_active;
|
||||
this.is_staff = object.is_staff;
|
||||
|
@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import moment from 'moment';
|
||||
import { Link } from '@reach/router';
|
||||
import { Link, navigate } from '@reach/router';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
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 = () => {
|
||||
this.props.onRepoClick(this.props.repo);
|
||||
}
|
||||
@ -317,12 +323,12 @@ class MylibRepoListItem extends React.Component {
|
||||
let repo = this.props.repo;
|
||||
let iconUrl = Utils.getLibIconUrl(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 (
|
||||
<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>
|
||||
<td onClick={this.visitRepo}><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||
<td onClick={this.visitRepo}>
|
||||
{this.state.isRenaming && (
|
||||
<Rename
|
||||
name={repo.repo_name}
|
||||
|
@ -29,6 +29,11 @@ class ResultsItem extends React.Component {
|
||||
let linkContent = decodeURI(item.fullpath).substring(1);
|
||||
let folderIconUrl = linkContent ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(true);
|
||||
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name, 192);
|
||||
|
||||
if (item.thumbnail_url !== '') {
|
||||
fileIconUrl = item.thumbnail_url;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="search-result-item">
|
||||
<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 moment from 'moment';
|
||||
import cookie from 'react-cookies';
|
||||
import { Link } from '@reach/router';
|
||||
import { Link, navigate } from '@reach/router';
|
||||
import { gettext, siteRoot, isPro } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
@ -194,6 +194,10 @@ class Item extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
visitRepo = () => {
|
||||
navigate(this.repoURL);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.unshared) {
|
||||
return null;
|
||||
@ -207,7 +211,7 @@ class Item extends Component {
|
||||
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
||||
let shareIconClassName = 'op-icon sf2-icon-share repo-share-btn' + 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 = (
|
||||
<Fragment>
|
||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||
@ -248,8 +252,8 @@ class Item extends Component {
|
||||
const mobileItem = (
|
||||
<Fragment>
|
||||
<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>
|
||||
<td onClick={this.visitRepo}><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
||||
<td onClick={this.visitRepo}>
|
||||
<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">{data.size}</span>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||
import { Link } from '@reach/router';
|
||||
import { Link, navigate } from '@reach/router';
|
||||
import moment from 'moment';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
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() {
|
||||
|
||||
if (this.state.unstarred) {
|
||||
@ -206,14 +215,14 @@ class Item extends Component {
|
||||
|
||||
const mobileItem = (
|
||||
<tr>
|
||||
<td className="text-center">
|
||||
<td className="text-center" onClick={this.visitItem}>
|
||||
{
|
||||
data.thumbnail_url ?
|
||||
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
||||
<img src={data.item_icon_url} alt={gettext('icon')} width="24" />
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<td onClick={this.visitItem}>
|
||||
{ data.is_dir ?
|
||||
<Link to={data.dirent_view_url}>{data.obj_name}</Link> :
|
||||
<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 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) {
|
||||
columns.push(
|
||||
{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') : '--'} /`}
|
||||
<br />
|
||||
{`${item.last_login ? moment(item.last_login).fromNow() : '--'}`}
|
||||
<br />
|
||||
{`${item.last_access_time ? moment(item.last_access_time).fromNow() : '--'}`}
|
||||
</td>
|
||||
<td>
|
||||
{(item.email != username && isOpIconShown) &&
|
||||
|
@ -3,6 +3,7 @@ export const gettext = window.gettext;
|
||||
|
||||
export const siteRoot = window.app.config.siteRoot;
|
||||
export const loginUrl = window.app.config.loginUrl;
|
||||
export const logoutUrl = window.app.config.logoutUrl;
|
||||
export const avatarInfo = window.app.config.avatarInfo;
|
||||
export const logoPath = window.app.config.logoPath;
|
||||
export const mediaUrl = window.app.config.mediaUrl;
|
||||
|
@ -484,7 +484,7 @@ export const Utils = {
|
||||
return list;
|
||||
},
|
||||
|
||||
getFileOperationList: function(currentRepoInfo, dirent, isContextmenu) {
|
||||
getFileOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
|
||||
let list = [];
|
||||
const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK,
|
||||
COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT } = TextTranslation;
|
||||
@ -516,7 +516,7 @@ export const Utils = {
|
||||
|
||||
if (isPro) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
@ -551,7 +551,7 @@ export const Utils = {
|
||||
getDirentOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
|
||||
return dirent.type == 'dir' ?
|
||||
Utils.getFolderOperationList(isRepoOwner, currentRepoInfo, dirent, isContextmenu) :
|
||||
Utils.getFileOperationList(currentRepoInfo, dirent, isContextmenu);
|
||||
Utils.getFileOperationList(isRepoOwner, currentRepoInfo, dirent, isContextmenu);
|
||||
},
|
||||
|
||||
sharePerms: function(permission) {
|
||||
|
@ -2,7 +2,6 @@ Django==2.2.14
|
||||
future
|
||||
captcha
|
||||
django-statici18n
|
||||
django-post_office==3.3.0
|
||||
django-webpack_loader
|
||||
gunicorn
|
||||
mysqlclient
|
||||
|
@ -19,6 +19,7 @@ from seaserv import seafile_api, ccnet_api
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error, to_python_boolean
|
||||
from seahub.api2.models import TokenV2
|
||||
|
||||
import seahub.settings as settings
|
||||
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, \
|
||||
is_pro_version, normalize_cache_key, is_valid_email, \
|
||||
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.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.repo import normalize_repo_status_code
|
||||
from seahub.constants import DEFAULT_ADMIN
|
||||
@ -57,6 +61,43 @@ logger = logging.getLogger(__name__)
|
||||
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):
|
||||
data = {}
|
||||
|
||||
@ -204,6 +245,7 @@ def update_user_info(request, user, password, is_active, is_staff, role,
|
||||
logger.error(e)
|
||||
seafile_api.set_user_quota(email, -1)
|
||||
|
||||
|
||||
def get_user_info(email):
|
||||
|
||||
user = User.objects.get(email=email)
|
||||
@ -297,8 +339,15 @@ class AdminAdminUsers(APIView):
|
||||
user_info['quota_total'] = -1
|
||||
|
||||
user_info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)
|
||||
|
||||
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:
|
||||
admin_role = AdminRole.objects.get_admin_role(user.email)
|
||||
@ -312,6 +361,7 @@ class AdminAdminUsers(APIView):
|
||||
}
|
||||
return Response(result)
|
||||
|
||||
|
||||
class AdminUsers(APIView):
|
||||
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
@ -363,7 +413,13 @@ class AdminUsers(APIView):
|
||||
info['quota_total'] = seafile_api.get_user_quota(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)
|
||||
|
||||
@ -423,8 +479,10 @@ class AdminUsers(APIView):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
try:
|
||||
data = self.get_info_of_users_order_by_quota_usage(source, direction,
|
||||
page, per_page)
|
||||
data = self.get_info_of_users_order_by_quota_usage(source,
|
||||
direction,
|
||||
page,
|
||||
per_page)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
@ -448,8 +506,10 @@ class AdminUsers(APIView):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
try:
|
||||
data = self.get_info_of_users_order_by_quota_usage(source, direction,
|
||||
page, per_page)
|
||||
data = self.get_info_of_users_order_by_quota_usage(source,
|
||||
direction,
|
||||
page,
|
||||
per_page)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
@ -489,10 +549,19 @@ class AdminUsers(APIView):
|
||||
info['quota_usage'] = -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['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):
|
||||
info['institution'] = profile.institution if profile else ''
|
||||
|
||||
@ -569,8 +638,7 @@ class AdminUsers(APIView):
|
||||
|
||||
if is_org_context(request):
|
||||
org_id = request.user.org.org_id
|
||||
org_quota_mb = seafile_api.get_org_quota(org_id) / \
|
||||
get_file_size_unit('MB')
|
||||
org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB')
|
||||
|
||||
if quota_total_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,
|
||||
None,
|
||||
[email2contact_email(email)])
|
||||
|
||||
add_user_tip = _('Successfully added user %(user)s. An email notification has been sent.') % {'user': email}
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
@ -669,8 +738,16 @@ class AdminLDAPUsers(APIView):
|
||||
info['quota_total'] = seafile_api.get_user_quota(user.email)
|
||||
info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
|
||||
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 ''
|
||||
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)
|
||||
|
||||
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['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 ''
|
||||
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)
|
||||
|
||||
if getattr(settings, 'MULTI_INSTITUTION', False):
|
||||
@ -984,8 +1069,7 @@ class AdminUser(APIView):
|
||||
|
||||
if is_org_context(request):
|
||||
org_id = request.user.org.org_id
|
||||
org_quota_mb = seafile_api.get_org_quota(org_id) / \
|
||||
get_file_size_unit('MB')
|
||||
org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB')
|
||||
|
||||
if quota_total_mb > org_quota_mb:
|
||||
error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
|
||||
@ -1115,7 +1199,7 @@ class AdminUserResetPassword(APIView):
|
||||
contact_email = Profile.objects.get_contact_email_by_user(email)
|
||||
try:
|
||||
send_html_email(_(u'Password has been reset on %s') % get_site_name(),
|
||||
'sysadmin/user_reset_email.html', c, None, [contact_email])
|
||||
'sysadmin/user_reset_email.html', c, None, [contact_email])
|
||||
reset_tip = _('Successfully reset password to %(passwd)s, an email has been sent to %(user)s.') % \
|
||||
{'passwd': new_password, 'user': contact_email}
|
||||
except Exception as e:
|
||||
@ -1299,7 +1383,7 @@ class AdminUserBeSharedRepos(APIView):
|
||||
if email not in nickname_dict:
|
||||
if '@seafile_group' in email:
|
||||
group_id = get_group_id_by_repo_owner(email)
|
||||
group_name= group_id_to_name(group_id)
|
||||
group_name = group_id_to_name(group_id)
|
||||
nickname_dict[email] = group_name
|
||||
else:
|
||||
nickname_dict[email] = email2nickname(email)
|
||||
|
@ -15,7 +15,6 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
from seaserv import seafile_api
|
||||
|
||||
import seahub.settings as settings
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
@ -218,7 +217,7 @@ class AdminUsersBatch(APIView):
|
||||
"email": email,
|
||||
}
|
||||
admin_operation.send(sender=None, admin_name=request.user.username,
|
||||
operation=USER_DELETE, detail=admin_op_detail)
|
||||
operation=USER_DELETE, detail=admin_op_detail)
|
||||
|
||||
if operation == 'set-institution':
|
||||
institution = request.POST.get('institution', None)
|
||||
@ -228,7 +227,7 @@ class AdminUsersBatch(APIView):
|
||||
|
||||
if institution != '':
|
||||
try:
|
||||
obj_insti = Institution.objects.get(name=institution)
|
||||
Institution.objects.get(name=institution)
|
||||
except Institution.DoesNotExist:
|
||||
error_msg = 'Institution %s does not exist' % institution
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
@ -372,14 +371,14 @@ class AdminImportUsers(APIView):
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
send_html_email_with_dj_template(
|
||||
email, dj_template='sysadmin/user_batch_add_email.html',
|
||||
subject=_('You are invited to join %s') % get_site_name(),
|
||||
context={
|
||||
'user': email2nickname(request.user.username),
|
||||
'email': email,
|
||||
'password': password,
|
||||
})
|
||||
send_html_email_with_dj_template(email,
|
||||
subject=_('You are invited to join %s') % get_site_name(),
|
||||
dj_template='sysadmin/user_batch_add_email.html',
|
||||
context={
|
||||
'user': email2nickname(request.user.username),
|
||||
'email': email,
|
||||
'password': password
|
||||
})
|
||||
|
||||
user = User.objects.get(email=email)
|
||||
|
||||
@ -407,4 +406,3 @@ class AdminImportUsers(APIView):
|
||||
operation=USER_ADD, detail=admin_op_detail)
|
||||
|
||||
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.views.file import can_preview_file, can_edit_file
|
||||
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.settings import MAX_UPLOAD_FILE_NAME_LEN, \
|
||||
@ -57,7 +57,7 @@ class FileView(APIView):
|
||||
file_name = file_obj.obj_name
|
||||
file_size = file_obj.size
|
||||
can_preview, error_msg = can_preview_file(file_name, file_size, repo)
|
||||
can_edit, error_msg = can_edit_file(file_name, file_size, repo)
|
||||
can_edit, error_msg = can_edit_file(file_name, file_size, repo)
|
||||
else:
|
||||
can_preview = False
|
||||
can_edit = False
|
||||
@ -295,11 +295,9 @@ class FileView(APIView):
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
# rename file
|
||||
new_file_name = check_filename_with_rename(repo_id, parent_dir,
|
||||
new_file_name)
|
||||
new_file_name = check_filename_with_rename(repo_id, parent_dir, new_file_name)
|
||||
try:
|
||||
seafile_api.rename_file(repo_id, parent_dir, oldname,
|
||||
new_file_name, username)
|
||||
seafile_api.rename_file(repo_id, parent_dir, oldname, new_file_name, username)
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
@ -394,8 +392,8 @@ class FileView(APIView):
|
||||
new_file_name = check_filename_with_rename(dst_repo_id, dst_dir, filename)
|
||||
try:
|
||||
seafile_api.move_file(src_repo_id, src_dir, filename,
|
||||
dst_repo_id, dst_dir, new_file_name, replace=False,
|
||||
username=username, need_progress=0, synchronous=1)
|
||||
dst_repo_id, dst_dir, new_file_name, replace=False,
|
||||
username=username, need_progress=0, synchronous=1)
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
@ -460,7 +458,7 @@ class FileView(APIView):
|
||||
new_file_name = check_filename_with_rename(dst_repo_id, dst_dir, filename)
|
||||
try:
|
||||
seafile_api.copy_file(src_repo_id, src_dir, filename, dst_repo_id,
|
||||
dst_dir, new_file_name, username, 0, synchronous=1)
|
||||
dst_dir, new_file_name, username, 0, synchronous=1)
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
@ -586,7 +584,9 @@ class FileView(APIView):
|
||||
error_msg = _("File is not locked.")
|
||||
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
|
||||
try:
|
||||
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.invitations.models import Invitation
|
||||
from seahub.base.accounts import User
|
||||
from post_office.models import STATUS
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
json_content_type = 'application/json; charset=utf-8'
|
||||
|
||||
|
||||
def invitation_owner_check(func):
|
||||
"""Check whether user is the invitation inviter.
|
||||
"""
|
||||
@ -34,6 +34,7 @@ def invitation_owner_check(func):
|
||||
|
||||
return _decorated
|
||||
|
||||
|
||||
class InvitationView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated, CanInviteGuest)
|
||||
@ -108,13 +109,12 @@ class InvitationRevokeView(APIView):
|
||||
'site_name': site_name,
|
||||
}
|
||||
|
||||
m = send_html_email_with_dj_template(
|
||||
email, dj_template='invitations/invitation_revoke_email.html',
|
||||
subject=subject,
|
||||
context=context,
|
||||
priority=MAIL_PRIORITY.now
|
||||
)
|
||||
if m.status != STATUS.sent:
|
||||
send_success = send_html_email_with_dj_template(email,
|
||||
subject=subject,
|
||||
dj_template='invitations/invitation_revoke_email.html',
|
||||
context=context)
|
||||
|
||||
if not send_success:
|
||||
logger.warning('send revoke access email to %s failed')
|
||||
|
||||
return Response({'success': True})
|
||||
|
@ -6,7 +6,6 @@ from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from post_office.models import STATUS
|
||||
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.permissions import CanInviteGuest
|
||||
@ -70,8 +69,9 @@ class InvitationsView(APIView):
|
||||
|
||||
i = Invitation.objects.add(inviter=request.user.username,
|
||||
accepter=accepter)
|
||||
m = i.send_to(email=accepter)
|
||||
if m.status == STATUS.sent:
|
||||
send_success = i.send_to(email=accepter)
|
||||
|
||||
if send_success:
|
||||
return Response(i.to_dict(), status=201)
|
||||
else:
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@ -119,11 +119,13 @@ class InvitationsBatchView(APIView):
|
||||
continue
|
||||
|
||||
if Invitation.objects.filter(inviter=request.user.username,
|
||||
accepter=accepter).count() > 0:
|
||||
accepter=accepter).count() > 0:
|
||||
|
||||
result['failed'].append({
|
||||
'email': accepter,
|
||||
'error_msg': _('%s is already invited.') % accepter
|
||||
})
|
||||
|
||||
continue
|
||||
|
||||
try:
|
||||
@ -139,13 +141,15 @@ class InvitationsBatchView(APIView):
|
||||
pass
|
||||
|
||||
i = Invitation.objects.add(inviter=request.user.username,
|
||||
accepter=accepter)
|
||||
accepter=accepter)
|
||||
|
||||
m = i.send_to(email=accepter)
|
||||
if m.status != STATUS.sent:
|
||||
send_success = i.send_to(email=accepter)
|
||||
|
||||
if not send_success:
|
||||
result['failed'].append({
|
||||
'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:
|
||||
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.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from seaserv import seafile_api
|
||||
|
||||
@ -14,8 +13,7 @@ from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.permissions import CanInviteGuest
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.utils import api_error
|
||||
from seahub.invitations.models import Invitation, RepoShareInvitation
|
||||
from post_office.models import STATUS
|
||||
from seahub.invitations.models import RepoShareInvitation
|
||||
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
|
||||
from seahub.share.utils import is_repo_admin
|
||||
from seahub.utils import is_org_context
|
||||
@ -82,7 +80,7 @@ class RepoShareInvitationView(APIView):
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
def delete(self, request, repo_id, format=None):
|
||||
@ -124,11 +122,11 @@ class RepoShareInvitationView(APIView):
|
||||
if not shared_obj:
|
||||
error_msg = 'repo share invitation not found.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
|
||||
shared_obj.delete()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
|
||||
return Response({'success': True})
|
||||
|
@ -8,7 +8,6 @@ from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from post_office.models import STATUS
|
||||
|
||||
from seaserv import seafile_api
|
||||
|
||||
@ -20,7 +19,6 @@ from seahub.base.accounts import User
|
||||
from seahub.utils import is_valid_email
|
||||
from seahub.invitations.models import Invitation, RepoShareInvitation
|
||||
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.share.utils import is_repo_admin
|
||||
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'
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RepoShareInvitationsView(APIView):
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated, CanInviteGuest)
|
||||
@ -47,7 +46,7 @@ class RepoShareInvitationsView(APIView):
|
||||
if not repo:
|
||||
error_msg = 'Library %s not found.' % repo_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
|
||||
if seafile_api.get_dir_id_by_path(repo.id, path) is None:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, 'Folder %s not found.' % path)
|
||||
|
||||
@ -124,7 +123,7 @@ class RepoShareInvitationsBatchView(APIView):
|
||||
if username != repo_owner and not is_repo_admin(username, repo_id):
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
|
||||
# main
|
||||
result = {}
|
||||
result['failed'] = []
|
||||
@ -173,7 +172,7 @@ class RepoShareInvitationsBatchView(APIView):
|
||||
continue
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
if invitation_queryset.filter(accepter=accepter).exists():
|
||||
invitation = invitation_queryset.filter(accepter=accepter)[0]
|
||||
else:
|
||||
@ -181,12 +180,12 @@ class RepoShareInvitationsBatchView(APIView):
|
||||
inviter=request.user.username, accepter=accepter)
|
||||
|
||||
if shared_queryset.filter(invitation=invitation).exists():
|
||||
result['failed'].append({
|
||||
'email': accepter,
|
||||
'error_msg': _('This item has been shared to %s.') % accepter
|
||||
})
|
||||
continue
|
||||
|
||||
result['failed'].append({
|
||||
'email': accepter,
|
||||
'error_msg': _('This item has been shared to %s.') % accepter
|
||||
})
|
||||
continue
|
||||
|
||||
try:
|
||||
RepoShareInvitation.objects.add(
|
||||
invitation=invitation, repo_id=repo_id, path=path, permission=permission)
|
||||
@ -203,11 +202,13 @@ class RepoShareInvitationsBatchView(APIView):
|
||||
|
||||
result['success'].append(data)
|
||||
|
||||
m = invitation.send_to(email=accepter)
|
||||
if m.status != STATUS.sent:
|
||||
send_sucess = invitation.send_to(email=accepter)
|
||||
|
||||
if not send_sucess:
|
||||
result['failed'].append({
|
||||
'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)
|
||||
|
@ -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, \
|
||||
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.devices import do_unlink_device
|
||||
from seahub.utils.repo import get_repo_owner, get_library_storages, \
|
||||
@ -424,6 +425,7 @@ class Search(APIView):
|
||||
throttle_classes = (UserRateThrottle, )
|
||||
|
||||
def get(self, request, format=None):
|
||||
|
||||
if not HAS_FILE_SEARCH:
|
||||
error_msg = 'Search not supported.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
@ -601,6 +603,16 @@ class Search(APIView):
|
||||
else:
|
||||
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
|
||||
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)
|
||||
|
||||
class ThumbnailView(APIView):
|
||||
authentication_classes = (TokenAuthentication,)
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
throttle_classes = (UserRateThrottle, )
|
||||
|
||||
|
@ -10,7 +10,6 @@ from django.utils.encoding import smart_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
import seaserv
|
||||
from seaserv import ccnet_threaded_rpc, unset_repo_passwd, \
|
||||
seafile_api, ccnet_api
|
||||
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
|
||||
from seahub.utils import is_user_password_strong, get_site_name, \
|
||||
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.share.models import ExtraSharePermission
|
||||
|
||||
@ -41,7 +40,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
ANONYMOUS_EMAIL = 'Anonymous'
|
||||
|
||||
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
|
||||
UNUSABLE_PASSWORD = '!' # This will never be a valid hash
|
||||
|
||||
|
||||
class UserManager(object):
|
||||
|
||||
@ -122,6 +122,7 @@ class UserManager(object):
|
||||
|
||||
return user
|
||||
|
||||
|
||||
class UserPermissions(object):
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
@ -222,6 +223,7 @@ class UserPermissions(object):
|
||||
|
||||
return self._get_perm_by_roles('can_publish_repo')
|
||||
|
||||
|
||||
class AdminPermissions(object):
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
@ -322,8 +324,8 @@ class User(object):
|
||||
return True
|
||||
|
||||
def save(self):
|
||||
emailuser = ccnet_threaded_rpc.get_emailuser(self.username)
|
||||
if emailuser:
|
||||
emailuser = ccnet_api.get_emailuser(self.username)
|
||||
if emailuser and emailuser.source.lower() in ("db", "ldapimport"):
|
||||
if not hasattr(self, 'password'):
|
||||
self.set_unusable_password()
|
||||
|
||||
@ -385,12 +387,10 @@ class User(object):
|
||||
if orgs:
|
||||
for org in orgs:
|
||||
org_id = org.org_id
|
||||
shared_in_repos = seafile_api.get_org_share_in_repo_list(org_id,
|
||||
username, -1, -1)
|
||||
shared_in_repos = seafile_api.get_org_share_in_repo_list(org_id, username, -1, -1)
|
||||
|
||||
for r in shared_in_repos:
|
||||
seafile_api.org_remove_share(org_id,
|
||||
r.repo_id, r.user, username)
|
||||
seafile_api.org_remove_share(org_id, r.repo_id, r.user, username)
|
||||
else:
|
||||
shared_in_repos = seafile_api.get_share_in_repo_list(username, -1, -1)
|
||||
for r in shared_in_repos:
|
||||
@ -478,15 +478,12 @@ class User(object):
|
||||
user_language = Profile.objects.get_user_language(u.email)
|
||||
translation.activate(user_language)
|
||||
|
||||
send_html_email_with_dj_template(
|
||||
u.email, dj_template='sysadmin/user_freeze_email.html',
|
||||
subject=_('Account %(account)s froze on %(site)s.') % {
|
||||
"account": self.email,
|
||||
"site": get_site_name(),
|
||||
},
|
||||
context={'user': self.email},
|
||||
priority=MAIL_PRIORITY.now
|
||||
)
|
||||
send_html_email_with_dj_template(u.email,
|
||||
subject=_('Account %(account)s froze on %(site)s.') % {
|
||||
"account": self.email,
|
||||
"site": get_site_name()},
|
||||
dj_template='sysadmin/user_freeze_email.html',
|
||||
context={'user': self.email})
|
||||
|
||||
# restore current language
|
||||
translation.activate(cur_language)
|
||||
@ -535,10 +532,11 @@ class User(object):
|
||||
for r in passwd_setted_repos:
|
||||
unset_repo_passwd(r.id, self.email)
|
||||
|
||||
|
||||
class AuthBackend(object):
|
||||
|
||||
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:
|
||||
raise User.DoesNotExist('User matching query does not exits.')
|
||||
|
||||
@ -580,7 +578,8 @@ class AuthBackend(object):
|
||||
if user.check_password(password):
|
||||
return user
|
||||
|
||||
########## Register related
|
||||
|
||||
# Register related
|
||||
class RegistrationBackend(object):
|
||||
"""
|
||||
A registration backend which follows a simple workflow:
|
||||
@ -653,10 +652,10 @@ class RegistrationBackend(object):
|
||||
# since user will be activated after registration,
|
||||
# so we will not use email sending, just create acitvated user
|
||||
new_user = RegistrationProfile.objects.create_active_user(username, email,
|
||||
password, site,
|
||||
send_email=False)
|
||||
password, site,
|
||||
send_email=False)
|
||||
# login the user
|
||||
new_user.backend=settings.AUTHENTICATION_BACKENDS[0]
|
||||
new_user.backend = settings.AUTHENTICATION_BACKENDS[0]
|
||||
|
||||
login(request, new_user)
|
||||
else:
|
||||
@ -702,7 +701,7 @@ class RegistrationBackend(object):
|
||||
user=activated,
|
||||
request=request)
|
||||
# login the user
|
||||
activated.backend=settings.AUTHENTICATION_BACKENDS[0]
|
||||
activated.backend = settings.AUTHENTICATION_BACKENDS[0]
|
||||
login(request, activated)
|
||||
|
||||
return activated
|
||||
@ -753,17 +752,17 @@ class RegistrationForm(forms.Form):
|
||||
Validates that the requested email is not already in use, and
|
||||
requires the password to be entered twice to catch typos.
|
||||
"""
|
||||
attrs_dict = { 'class': 'input' }
|
||||
attrs_dict = {'class': 'input'}
|
||||
|
||||
email = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=75)),
|
||||
label=_("Email address"))
|
||||
|
||||
email = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict,
|
||||
maxlength=75)),
|
||||
label=_("Email address"))
|
||||
userid = forms.RegexField(regex=r'^\w+$',
|
||||
max_length=40,
|
||||
required=False,
|
||||
widget=forms.TextInput(),
|
||||
label=_("Username"),
|
||||
error_messages={ 'invalid': _("This value must be of length 40") })
|
||||
error_messages={'invalid': _("This value must be of length 40")})
|
||||
|
||||
password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
|
||||
label=_("Password"))
|
||||
@ -825,12 +824,13 @@ class RegistrationForm(forms.Form):
|
||||
raise forms.ValidationError(_("The two password fields didn't match."))
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class DetailedRegistrationForm(RegistrationForm):
|
||||
attrs_dict = { 'class': 'input' }
|
||||
attrs_dict = {'class': 'input'}
|
||||
|
||||
try:
|
||||
from seahub.settings import REGISTRATION_DETAILS_MAP
|
||||
except:
|
||||
except ImportError:
|
||||
REGISTRATION_DETAILS_MAP = None
|
||||
|
||||
if REGISTRATION_DETAILS_MAP:
|
||||
|
@ -133,6 +133,7 @@ def base(request):
|
||||
'constance_enabled': dj_settings.CONSTANCE_ENABLED,
|
||||
'FILE_SERVER_ROOT': file_server_root,
|
||||
'LOGIN_URL': dj_settings.LOGIN_URL,
|
||||
'LOGOUT_URL': dj_settings.LOGOUT_URL,
|
||||
'enable_thumbnail': ENABLE_THUMBNAIL,
|
||||
'thumbnail_size_for_original': THUMBNAIL_SIZE_FOR_ORIGINAL,
|
||||
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
|
||||
|
@ -2,7 +2,6 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
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.utils import gen_token, get_site_name
|
||||
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
|
||||
|
||||
GUEST = 'Guest'
|
||||
|
||||
|
||||
class InvitationManager(models.Manager):
|
||||
def add(self, inviter, accepter, invite_type=GUEST):
|
||||
token = gen_token(max_length=32)
|
||||
@ -26,8 +26,7 @@ class InvitationManager(models.Manager):
|
||||
return i
|
||||
|
||||
def get_by_inviter(self, inviter):
|
||||
return super(InvitationManager,
|
||||
self).filter(inviter=inviter).order_by('-invite_time')
|
||||
return super(InvitationManager, self).filter(inviter=inviter).order_by('-invite_time')
|
||||
|
||||
def delete_all_expire_invitation(self):
|
||||
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['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.') % {
|
||||
'user': self.inviter, 'site_name': get_site_name()}
|
||||
return send_html_email_with_dj_template(
|
||||
email, dj_template='invitations/invitation_email.html',
|
||||
context=context,
|
||||
subject=subject,
|
||||
priority=MAIL_PRIORITY.now
|
||||
)
|
||||
|
||||
return send_html_email_with_dj_template(email,
|
||||
subject=subject,
|
||||
dj_template='invitations/invitation_email.html',
|
||||
context=context)
|
||||
|
||||
|
||||
class RepoShareInvitationManager(models.Manager):
|
||||
@ -115,7 +111,7 @@ class RepoShareInvitationManager(models.Manager):
|
||||
)
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
||||
def list_by_repo_id_and_path(self, repo_id, path):
|
||||
return self.select_related('invitation').filter(
|
||||
invitation__expire_time__gte=timezone.now(),
|
||||
@ -132,11 +128,13 @@ class RepoShareInvitationManager(models.Manager):
|
||||
return qs[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def list_by_invitation(self, invitation):
|
||||
return self.select_related('invitation').filter(invitation=invitation)
|
||||
|
||||
|
||||
class RepoShareInvitation(models.Model):
|
||||
|
||||
PERMISSION_CHOICES = (
|
||||
(PERMISSION_READ, 'read only'),
|
||||
(PERMISSION_READ_WRITE, 'read and write')
|
||||
|
@ -12,13 +12,15 @@ import seaserv
|
||||
from seaserv import seafile_api
|
||||
|
||||
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
|
||||
|
||||
# Get an instance of a logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = 'Send Email notifications to admins if there are virus files detected .'
|
||||
label = "notifications_notify_admins_on_virus"
|
||||
|
||||
@ -52,11 +54,9 @@ class Command(BaseCommand):
|
||||
user_language = self.get_user_language(u.email)
|
||||
translation.activate(user_language)
|
||||
|
||||
send_html_email_with_dj_template(
|
||||
u.email, dj_template='notifications/notify_virus.html',
|
||||
subject=_('Virus detected on %s') % get_site_name(),
|
||||
priority=MAIL_PRIORITY.now
|
||||
)
|
||||
send_html_email_with_dj_template(u.email,
|
||||
subject=_('Virus detected on %s') % get_site_name(),
|
||||
dj_template='notifications/notify_virus.html')
|
||||
|
||||
# restore current language
|
||||
translation.activate(cur_language)
|
||||
@ -68,11 +68,9 @@ class Command(BaseCommand):
|
||||
return
|
||||
|
||||
for mail in notify_list:
|
||||
send_html_email_with_dj_template(
|
||||
mail, dj_template='notifications/notify_virus.html',
|
||||
subject=_('Virus detected on %s') % get_site_name(),
|
||||
priority=MAIL_PRIORITY.now
|
||||
)
|
||||
send_html_email_with_dj_template(mail,
|
||||
subject=_('Virus detected on %s') % get_site_name(),
|
||||
dj_template='notifications/notify_virus.html')
|
||||
|
||||
def email_repo_owner(self, repo_file):
|
||||
repo_id, file_path = repo_file.split(':', 1)
|
||||
@ -88,16 +86,12 @@ class Command(BaseCommand):
|
||||
translation.activate(user_language)
|
||||
|
||||
contact_email = Profile.objects.get_contact_email_by_user(owner)
|
||||
send_html_email_with_dj_template(
|
||||
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(),
|
||||
priority=MAIL_PRIORITY.now
|
||||
)
|
||||
send_html_email_with_dj_template(contact_email,
|
||||
subject=_('Virus detected on %s') % get_site_name(),
|
||||
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
|
||||
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
|
||||
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',
|
||||
'constance',
|
||||
'constance.backends.database',
|
||||
'post_office',
|
||||
'termsandconditions',
|
||||
'webpack_loader',
|
||||
|
||||
|
@ -3,34 +3,30 @@
|
||||
import os
|
||||
import logging
|
||||
import json
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from constance import config
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404, \
|
||||
HttpResponseBadRequest
|
||||
from django.utils.translation import ugettext as _, activate
|
||||
from django.contrib import messages
|
||||
from django.utils import timezone
|
||||
from django.utils.html import escape
|
||||
|
||||
import seaserv
|
||||
from seaserv import seafile_api
|
||||
from seaserv import ccnet_threaded_rpc
|
||||
from pysearpc import SearpcError
|
||||
|
||||
from seahub.share.forms import FileLinkShareForm, \
|
||||
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.auth.decorators import login_required, login_required_ajax
|
||||
from seahub.base.decorators import require_POST
|
||||
from seahub.contacts.signals import mail_sended
|
||||
from seahub.views import is_registered_user, check_folder_permission
|
||||
from seahub.utils import string2list, gen_shared_link, \
|
||||
gen_shared_upload_link, IS_EMAIL_CONFIGURED, check_filename_with_rename, \
|
||||
from seahub.utils import string2list, IS_EMAIL_CONFIGURED, check_filename_with_rename, \
|
||||
is_valid_username, is_valid_email, send_html_email, is_org_context, \
|
||||
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, \
|
||||
ADD_REPLY_TO_HEADER, SHARE_LINK_EMAIL_LANGUAGE, \
|
||||
SHARE_LINK_AUDIT_CODE_TIMEOUT
|
||||
@ -39,21 +35,25 @@ from seahub.profile.models import Profile
|
||||
# Get an instance of a logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
########## rpc wrapper
|
||||
|
||||
# rpc wrapper
|
||||
def is_org_repo_owner(username, repo_id):
|
||||
owner = seaserv.seafserv_threaded_rpc.get_org_repo_owner(repo_id)
|
||||
return True if owner == username else False
|
||||
|
||||
|
||||
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,
|
||||
from_user, to_user,
|
||||
permission)
|
||||
|
||||
|
||||
def org_remove_share(org_id, repo_id, from_user, to_user):
|
||||
return seaserv.seafserv_threaded_rpc.org_remove_share(org_id, repo_id,
|
||||
from_user, to_user)
|
||||
|
||||
########## functions
|
||||
|
||||
# functions
|
||||
def share_to_group(request, repo, group, permission):
|
||||
"""Share repo to group with given permission.
|
||||
"""
|
||||
@ -83,6 +83,7 @@ def share_to_group(request, repo, group, permission):
|
||||
logger.error(e)
|
||||
return False
|
||||
|
||||
|
||||
def share_to_user(request, repo, to_user, permission):
|
||||
"""Share repo to a user with given permission.
|
||||
"""
|
||||
@ -109,8 +110,8 @@ def share_to_user(request, repo, to_user, permission):
|
||||
else:
|
||||
seafile_api.share_repo(repo_id, from_user, to_user, permission)
|
||||
except SearpcError as e:
|
||||
return False
|
||||
logger.error(e)
|
||||
logger.error(e)
|
||||
return False
|
||||
else:
|
||||
# send a signal when sharing repo successful
|
||||
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)
|
||||
return True
|
||||
|
||||
########## share link
|
||||
|
||||
# share link
|
||||
@login_required_ajax
|
||||
def send_shared_link(request):
|
||||
"""
|
||||
@ -131,7 +133,8 @@ def send_shared_link(request):
|
||||
content_type = 'application/json; charset=utf-8'
|
||||
|
||||
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)
|
||||
|
||||
form = FileLinkShareForm(request.POST)
|
||||
@ -207,6 +210,7 @@ def send_shared_link(request):
|
||||
return HttpResponseBadRequest(json.dumps(form.errors),
|
||||
content_type=content_type)
|
||||
|
||||
|
||||
@login_required
|
||||
def save_shared_link(request):
|
||||
"""Save public share link to one's library.
|
||||
@ -246,6 +250,7 @@ def save_shared_link(request):
|
||||
messages.success(request, _('Successfully saved.'))
|
||||
return HttpResponseRedirect(next_page)
|
||||
|
||||
|
||||
@login_required_ajax
|
||||
def send_shared_upload_link(request):
|
||||
"""
|
||||
@ -257,7 +262,8 @@ def send_shared_upload_link(request):
|
||||
content_type = 'application/json; charset=utf-8'
|
||||
|
||||
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)
|
||||
|
||||
form = UploadLinkShareForm(request.POST)
|
||||
@ -317,6 +323,7 @@ def send_shared_upload_link(request):
|
||||
return HttpResponseBadRequest(json.dumps(form.errors),
|
||||
content_type=content_type)
|
||||
|
||||
|
||||
@login_required_ajax
|
||||
@require_POST
|
||||
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(
|
||||
org_id, repo_id, path, name, name, username)
|
||||
else:
|
||||
sub_repo_id = seafile_api.create_virtual_repo(repo_id, path,
|
||||
name, name, username)
|
||||
sub_repo_id = seafile_api.create_virtual_repo(repo_id, path, name, name, username)
|
||||
sub_repo = seafile_api.get_repo(sub_repo_id)
|
||||
except SearpcError as e:
|
||||
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")})
|
||||
return HttpResponse(data, status=400, content_type=content_type)
|
||||
|
||||
|
||||
def ajax_get_link_audit_code(request):
|
||||
"""
|
||||
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
|
||||
subject = _("Verification code for visiting share links")
|
||||
c = {
|
||||
'code': code,
|
||||
}
|
||||
try:
|
||||
send_html_email_with_dj_template(
|
||||
email, dj_template='share/audit_code_email.html',
|
||||
context=c, subject=subject, priority=MAIL_PRIORITY.now
|
||||
)
|
||||
return HttpResponse(json.dumps({'success': True}), status=200,
|
||||
content_type=content_type)
|
||||
except Exception as e:
|
||||
c = {'code': code}
|
||||
|
||||
send_success = send_html_email_with_dj_template(email,
|
||||
subject=subject,
|
||||
dj_template='share/audit_code_email.html',
|
||||
context=c)
|
||||
|
||||
if not send_success:
|
||||
logger.error('Failed to send audit code via email to %s')
|
||||
logger.error(e)
|
||||
return HttpResponse(json.dumps({
|
||||
"error": _("Failed to send a verification code, please try again later.")
|
||||
}), 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 }}',
|
||||
siteRoot: '{{ SITE_ROOT }}',
|
||||
loginUrl: '{{ LOGIN_URL }}',
|
||||
logoutUrl: '{{ LOGOUT_URL }}',
|
||||
isPro: '{{ is_pro }}',
|
||||
isDocs: '{{ is_docs }}',
|
||||
lang: '{{ LANGUAGE_CODE }}',
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% extends "base_for_react.html" %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
{% load seahub_tags %}
|
||||
|
||||
{% block extra_style %}
|
||||
{% render_bundle 'app' 'css' %}
|
||||
@ -16,6 +17,15 @@
|
||||
uploadLinkExpireDaysMin: {{ upload_link_expire_days_min }},
|
||||
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>
|
||||
{% render_bundle 'app' 'js' %}
|
||||
{% endblock %}
|
||||
|
@ -1,27 +1,23 @@
|
||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||
import os
|
||||
from django.template import Context, loader
|
||||
from post_office import mail
|
||||
from post_office.models import PRIORITY
|
||||
from constance import config
|
||||
import logging
|
||||
from django.template import loader
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
from seahub.utils import get_site_scheme_and_netloc, get_site_name
|
||||
from seahub.settings import MEDIA_URL, 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,
|
||||
message='', headers=None,
|
||||
priority=None, backend=''):
|
||||
|
||||
def send_html_email_with_dj_template(recipients, subject, dj_template, context={}):
|
||||
"""
|
||||
|
||||
Arguments:
|
||||
- `recipients`:
|
||||
- `subject`:
|
||||
- `sender`:
|
||||
- `template`:
|
||||
- `dj_template`:
|
||||
- `context`:
|
||||
|
||||
"""
|
||||
@ -42,7 +38,12 @@ def send_html_email_with_dj_template(recipients, subject, dj_template,
|
||||
t = loader.get_template(dj_template)
|
||||
html_message = t.render(context)
|
||||
|
||||
return mail.send(recipients, sender=sender, template=template, context=context,
|
||||
subject=subject, message=message,
|
||||
html_message=html_message, headers=headers, priority=priority,
|
||||
backend=backend)
|
||||
mail = EmailMessage(subject=subject, body=html_message, to=[recipients])
|
||||
mail.content_subtype = "html"
|
||||
|
||||
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
|
||||
|
||||
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.constants import HASH_URLS, PERMISSION_READ
|
||||
|
||||
@ -1180,6 +1180,7 @@ def react_fake_view(request, **kwargs):
|
||||
max_upload_file_size = -1
|
||||
|
||||
return render(request, "react_app.html", {
|
||||
"onlyoffice_desktop_editors_portal_login": ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN,
|
||||
"guide_enabled": guide_enabled,
|
||||
'trash_repos_expire_days': expire_days if expire_days > 0 else 30,
|
||||
'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.rpc import mute_seafile_api
|
||||
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.user_permissions import get_basic_user_roles, \
|
||||
get_user_role, get_basic_admin_roles
|
||||
|
Loading…
Reference in New Issue
Block a user