mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-16 23:29:49 +00:00
[system admin] users: rewrote user pages & 'ldap users' page (#4216)
This commit is contained in:
@@ -116,7 +116,7 @@ class Account extends Component {
|
||||
} else {
|
||||
if (isStaff) {
|
||||
data = {
|
||||
url: `${siteRoot}sys/useradmin/`,
|
||||
url: `${siteRoot}sys/info/`,
|
||||
text: gettext('System Admin')
|
||||
};
|
||||
} else if (isOrgStaff) {
|
||||
|
@@ -41,6 +41,7 @@ class SysAdminImportUserDialog extends React.Component {
|
||||
}
|
||||
const file = this.fileInputRef.current.files[0];
|
||||
this.props.importUserInBatch(file);
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -49,8 +50,7 @@ class SysAdminImportUserDialog extends React.Component {
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Import users from a .xlsx file')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<a href={`${siteRoot}useradmin/batchadduser/example/`}>{gettext('Download an example file')}</a>
|
||||
<br/>
|
||||
<p><a className="text-secondary small" href={`${siteRoot}useradmin/batchadduser/example/`}>{gettext('Download an example file')}</a></p>
|
||||
<button className="btn btn-outline-primary" onClick={this.openFileInput}>{gettext('Upload file')}</button>
|
||||
<input className="d-none" type="file" onChange={this.uploadFile} ref={this.fileInputRef} />
|
||||
{errorMsg && <Alert color="danger">{errorMsg}</Alert>}
|
||||
|
@@ -53,7 +53,6 @@ class SysAdminSetOrgNameDialog extends React.Component {
|
||||
<FormGroup>
|
||||
<Input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={name}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onChange={this.handleInputChange}
|
||||
|
@@ -1,79 +0,0 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Label, Input } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onContactEmailChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SysAdminUserSetContactEmailDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
contactEmail: '',
|
||||
isSubmitBtnActive: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.props.toggle();
|
||||
}
|
||||
|
||||
handleContactEmailChange = (e) => {
|
||||
this.setState({contactEmail: e.target.value.trim()});
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
let { contactEmail } = this.state;
|
||||
if(Utils.isValidEmail(contactEmail) || contactEmail === '') {
|
||||
this.props.onContactEmailChanged(contactEmail);
|
||||
} else {
|
||||
this.setState({
|
||||
errorMsg: gettext('Contact email invalid.')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { contactEmail, errorMsg } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Set user contact email')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Label for="repoName">{gettext('Name')}</Label>
|
||||
<Input
|
||||
id="repoName"
|
||||
onKeyPress={this.handleKeyPress}
|
||||
value={contactEmail}
|
||||
onChange={this.handleContactEmailChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
{errorMsg && <Alert color="danger">{errorMsg}</Alert>}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SysAdminUserSetContactEmailDialog.propTypes = propTypes;
|
||||
|
||||
export default SysAdminUserSetContactEmailDialog;
|
@@ -1,68 +0,0 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Input } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onLoginIDChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SysAdminUserSetLoginIDDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loginID: '',
|
||||
};
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.props.toggle();
|
||||
}
|
||||
|
||||
handleLoginIDChange = (e) => {
|
||||
this.setState({loginID: e.target.value.trim()});
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
let { loginID } = this.state;
|
||||
this.props.onLoginIDChanged(loginID);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { loginID } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Set user Login ID')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Input
|
||||
id="repoName"
|
||||
onKeyPress={this.handleKeyPress}
|
||||
value={loginID}
|
||||
onChange={this.handleLoginIDChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SysAdminUserSetLoginIDDialog.propTypes = propTypes;
|
||||
|
||||
export default SysAdminUserSetLoginIDDialog;
|
@@ -1,68 +0,0 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Label, Input } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onNameChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SysAdminUserSetNameDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.props.toggle();
|
||||
}
|
||||
|
||||
handleNameChange = (e) => {
|
||||
this.setState({name: e.target.value.trim()});
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
let { name } = this.state;
|
||||
this.props.onNameChanged(name);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { name } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Set user name')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Input
|
||||
id="repoName"
|
||||
onKeyPress={this.handleKeyPress}
|
||||
value={name}
|
||||
onChange={this.handleNameChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SysAdminUserSetNameDialog.propTypes = propTypes;
|
||||
|
||||
export default SysAdminUserSetNameDialog;
|
@@ -1,88 +0,0 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Label, Input } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onQuotaChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SysAdminUserSetQuotaDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
quota: '',
|
||||
isSubmitBtnActive: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.props.toggle();
|
||||
}
|
||||
|
||||
handleQuotaChange = (e) => {
|
||||
if (!e.target.value.trim()) {
|
||||
this.setState({isSubmitBtnActive: false});
|
||||
} else {
|
||||
this.setState({
|
||||
isSubmitBtnActive: true,
|
||||
errorMsg: ''
|
||||
});
|
||||
}
|
||||
this.setState({quota: e.target.value});
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
let { quota } = this.state;
|
||||
if(Utils.isInteger(quota) && quota >= 0) {
|
||||
this.props.onQuotaChanged(quota);
|
||||
} else {
|
||||
this.setState({
|
||||
errorMsg: gettext('Invalid quota.')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { quota, isSubmitBtnActive, errorMsg } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Set quota')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Label for="repoName">{gettext('Name')}</Label>
|
||||
<Input
|
||||
id="repoName"
|
||||
onKeyPress={this.handleKeyPress}
|
||||
value={quota}
|
||||
onChange={this.handleQuotaChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
<Alert color="light">{gettext('An integer that is greater than or equal to 0.')}{gettext('Tip: 0 means default limit')}</Alert>
|
||||
{errorMsg && <Alert color="danger">{errorMsg}</Alert>}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit} disabled={!isSubmitBtnActive}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SysAdminUserSetQuotaDialog.propTypes = propTypes;
|
||||
|
||||
export default SysAdminUserSetQuotaDialog;
|
@@ -1,71 +0,0 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Label, Input } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
toggle: PropTypes.func.isRequired,
|
||||
onReferenceIDChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SysAdminUserSetReferenceIDDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
referenceID: '',
|
||||
isSubmitBtnActive: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.props.toggle();
|
||||
}
|
||||
|
||||
handleReferenceIDChange = (e) => {
|
||||
this.setState({referenceID: e.target.value.trim()});
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
let { referenceID } = this.state;
|
||||
this.props.onReferenceIDChanged(referenceID);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { referenceID } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Set user Reference ID')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Label for="repoName">{gettext('Name')}</Label>
|
||||
<Input
|
||||
id="repoName"
|
||||
onKeyPress={this.handleKeyPress}
|
||||
value={referenceID}
|
||||
onChange={this.handleReferenceIDChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SysAdminUserSetReferenceIDDialog.propTypes = propTypes;
|
||||
|
||||
export default SysAdminUserSetReferenceIDDialog;
|
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Alert, Modal, ModalHeader, ModalBody, ModalFooter, Button, Form, FormGroup, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
dialogTitle: PropTypes.string.isRequired,
|
||||
updateValue: PropTypes.func.isRequired,
|
||||
toggleDialog: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class UpdateUser extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: this.props.value,
|
||||
isSubmitBtnActive: false
|
||||
};
|
||||
}
|
||||
|
||||
handleInputChange = (e) => {
|
||||
const value = e.target.value.trim();
|
||||
this.setState({
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
if (e.key == 'Enter') {
|
||||
this.handleSubmit();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
this.props.updateValue(this.state.value);
|
||||
this.props.toggleDialog();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dialogTitle, toggleDialog } = this.props;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={toggleDialog}>
|
||||
<ModalHeader toggle={toggleDialog}>{this.props.dialogTitle}</ModalHeader>
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Input
|
||||
type="text"
|
||||
value={this.state.value}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={toggleDialog}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateUser.propTypes = propTypes;
|
||||
|
||||
export default UpdateUser;
|
@@ -37,7 +37,11 @@ class SelectEditor extends React.Component {
|
||||
for (let i = 0, length = options.length; i < length; i++) {
|
||||
let option = {};
|
||||
option.value = options[i];
|
||||
option.label = <div>{this.props.translateOption(options[i])}{ this.props.translateExplanation && <div className="permission-editor-explanation">{this.props.translateExplanation(options[i])}</div>}</div>;
|
||||
if (!options[i].length) { // it's ''. for example, intitution option in 'system admin - users' page can be ''.
|
||||
option.label = <div style={{minHeight: '1em'}}></div>;
|
||||
} else {
|
||||
option.label = <div>{this.props.translateOption(options[i])}{ this.props.translateExplanation && <div className="permission-editor-explanation">{this.props.translateExplanation(options[i])}</div>}</div>;
|
||||
}
|
||||
this.options.push(option);
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Link } from '@reach/router';
|
||||
import { Button } from 'reactstrap';
|
||||
import moment from 'moment';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
@@ -170,18 +171,18 @@ class Item extends Component {
|
||||
let groupName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
||||
let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', groupName);
|
||||
|
||||
const libUrl = item.parent_group_id == 0 ?
|
||||
const groupUrl = item.parent_group_id == 0 ?
|
||||
`${siteRoot}sys/groups/${item.id}/libraries/` :
|
||||
`${siteRoot}sysadmin/#address-book/groups/${item.id}/`;
|
||||
`${siteRoot}sys/departments/${item.id}/`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<td><a href={libUrl}>{item.name}</a></td>
|
||||
<td><Link to={groupUrl}>{item.name}</Link></td>
|
||||
<td>
|
||||
{item.owner == 'system admin' ?
|
||||
'--' :
|
||||
<a href={`${siteRoot}useradmin/info/${encodeURIComponent(item.owner)}/`}>{item.owner_name}</a>
|
||||
<Link to={`${siteRoot}sys/users/${encodeURIComponent(item.owner)}/`}>{item.owner_name}</Link>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
|
@@ -14,8 +14,12 @@ import DeviceErrors from './devices/devices-errors';
|
||||
import Users from './users/users';
|
||||
import AdminUsers from './users/admin-users';
|
||||
import LDAPImportedUsers from './users/ldap-imported-users';
|
||||
import UsersLDAP from './users/users-ldap';
|
||||
import LDAPUsers from './users/ldap-users';
|
||||
import User from './users/user-info';
|
||||
import UserOwnedRepos from './users/user-repos';
|
||||
import UserSharedRepos from './users/user-shared-repos';
|
||||
import UserLinks from './users/user-links';
|
||||
import UserGroups from './users/user-groups';
|
||||
|
||||
import AllRepos from './repos/all-repos';
|
||||
import SystemRepo from './repos/system-repo';
|
||||
@@ -80,6 +84,10 @@ class SysAdmin extends React.Component {
|
||||
tab: 'libraries',
|
||||
urlPartList: ['all-libraries', 'system-library', 'trash-libraries', 'libraries/']
|
||||
},
|
||||
{
|
||||
tab: 'users',
|
||||
urlPartList: ['users/']
|
||||
},
|
||||
{
|
||||
tab: 'groups',
|
||||
urlPartList: ['groups/']
|
||||
@@ -164,8 +172,12 @@ class SysAdmin extends React.Component {
|
||||
<Users path={siteRoot + 'sys/users'} />
|
||||
<AdminUsers path={siteRoot + 'sys/users/admins'} />
|
||||
<LDAPImportedUsers path={siteRoot + 'sys/users/ldap-imported'} />
|
||||
<UsersLDAP path={siteRoot + 'sys/users/ldap'} />
|
||||
<LDAPUsers path={siteRoot + 'sys/users/ldap'} />
|
||||
<User path={siteRoot + 'sys/users/:email'} />
|
||||
<UserOwnedRepos path={siteRoot + 'sys/users/:email/owned-libraries'} />
|
||||
<UserSharedRepos path={siteRoot + 'sys/users/:email/shared-libraries'} />
|
||||
<UserLinks path={siteRoot + 'sys/users/:email/shared-links'} />
|
||||
<UserGroups path={siteRoot + 'sys/users/:email/groups'} />
|
||||
|
||||
<FileScanRecords
|
||||
path={siteRoot + 'sys/file-scan-records'}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Link } from '@reach/router';
|
||||
import { Button } from 'reactstrap';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
@@ -201,7 +202,7 @@ class Item extends Component {
|
||||
const { repo } = this.props;
|
||||
if (repo.name) {
|
||||
if (isPro && enableSysAdminViewRepo && !repo.encrypted) {
|
||||
return <a href={`${siteRoot}sys/libraries/${repo.id}/`}>{repo.name}</a>;
|
||||
return <Link to={`${siteRoot}sys/libraries/${repo.id}/`}>{repo.name}</Link>;
|
||||
} else {
|
||||
return repo.name;
|
||||
}
|
||||
@@ -229,8 +230,8 @@ class Item extends Component {
|
||||
<td>{repo.id}</td>
|
||||
<td>
|
||||
{isGroupOwnedRepo ?
|
||||
<a href={`${siteRoot}sysadmin/#address-book/groups/${repo.owner_name}/`}>{repo.group_name}</a> :
|
||||
<a href={`${siteRoot}useradmin/info/${encodeURIComponent(repo.owner_email)}/`}>{repo.owner_name}</a>
|
||||
<Link to={`${siteRoot}sys/departments/${repo.owner_name}/`}>{repo.group_name}</Link> :
|
||||
<Link to={`${siteRoot}sys/users/${encodeURIComponent(repo.owner_email)}/`}>{repo.owner_name}</Link>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import { Link } from '@reach/router';
|
||||
import moment from 'moment';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext, siteRoot, loginUrl } from '../../../utils/constants';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import Loading from '../../../components/loading';
|
||||
import Paginator from '../../../components/paginator';
|
||||
import UsersNav from './users-nav';
|
||||
@@ -13,8 +14,6 @@ class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
};
|
||||
}
|
||||
|
||||
getPreviousPage = () => {
|
||||
@@ -30,7 +29,7 @@ class Content extends Component {
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
@@ -42,23 +41,19 @@ class Content extends Component {
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="34%">{gettext('Email')}</th>
|
||||
<th width="33%">{gettext('Space Used')}{' / '}{gettext('Quota')}</th>
|
||||
<th width="33%">{gettext('Create At')}{' / '}{gettext('Last Login')}</th>
|
||||
<th width="40%">{gettext('Email')}</th>
|
||||
<th width="30%">{gettext('Space Used')}{' / '}{gettext('Quota')}</th>
|
||||
<th width="30%">{gettext('Last Login')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{items &&
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
deleteUser={this.props.deleteUser}
|
||||
onUserSelected={this.props.onUserSelected}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
}
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<Paginator
|
||||
gotoPreviousPage={this.getPreviousPage}
|
||||
@@ -81,39 +76,22 @@ class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
quota_total: this.props.item.quota_total,
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({isOpIconShown: true});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({isOpIconShown: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
let { status, role, quota_total, isOpIconShown } = this.state;
|
||||
let {item} = this.props;
|
||||
let iconVisibility = this.state.isOpIconShown ? '' : ' invisible';
|
||||
let pencilIconClassName = 'fa fa-pencil-alt attr-action-icon' + iconVisibility;
|
||||
|
||||
const { item } = this.props;
|
||||
let email = '<span class="op-target">' + Utils.HTMLescape(item.email) + '</span>';
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<tr>
|
||||
<td>
|
||||
<div><a href={siteRoot + 'sys/user-info/' + item.email + '/'}>{item.email}</a></div>
|
||||
</td>
|
||||
<td>{Utils.bytesToSize(item.quota_usage)}{' / '}
|
||||
{quota_total >= 0 ? Utils.bytesToSize(quota_total) : '--'}
|
||||
<Link to={`${siteRoot}sys/users/${encodeURIComponent(item.email)}/`}>{item.email}</Link>
|
||||
</td>
|
||||
<td>
|
||||
<div>{moment(item.create_time).format('YYYY-MM-DD HH:mm') }{' /'}</div>
|
||||
<div>{item.last_login == '' ? '--' : moment(item.last_login).fromNow()}</div>
|
||||
{`${Utils.bytesToSize(item.quota_usage)} / ${item.quota_total > 0 ? Utils.bytesToSize(item.quota_total) : '--'}`}
|
||||
</td>
|
||||
<td>
|
||||
{item.last_login ? moment(item.last_login).fromNow() : '--'}
|
||||
</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
@@ -121,7 +99,7 @@ class Item extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
class UsersLDAP extends Component {
|
||||
class Users extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -131,22 +109,22 @@ class UsersLDAP extends Component {
|
||||
userList: {},
|
||||
hasNextPage: false,
|
||||
currentPage: 1,
|
||||
perPage: 25,
|
||||
perPage: 25
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.getUsersListByPage(1); // init enter the first page
|
||||
this.getUsersListByPage(1);
|
||||
}
|
||||
|
||||
getUsersListByPage = (page) => {
|
||||
let { perPage } = this.state;
|
||||
seafileAPI.sysAdminListAllLDAPUsers(page, perPage).then(res => {
|
||||
seafileAPI.sysAdminListLDAPUsers(page, perPage).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
userList: res.data.ldap_user_list,
|
||||
hasNextPage: res.data.has_next_page,
|
||||
loading: false,
|
||||
currentPage: page,
|
||||
currentPage: page
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
@@ -155,6 +133,7 @@ class UsersLDAP extends Component {
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
@@ -179,11 +158,9 @@ class UsersLDAP extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
//let { } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar>
|
||||
</MainPanelTopbar>
|
||||
<MainPanelTopbar />
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<UsersNav currentItem="ldap" />
|
||||
@@ -206,4 +183,4 @@ class UsersLDAP extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default UsersLDAP;
|
||||
export default Users;
|
@@ -1,17 +1,32 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import { Link } from '@reach/router';
|
||||
import moment from 'moment';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { siteRoot, loginUrl, gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import Loading from '../../../components/loading';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import Nav from './user-nav';
|
||||
import OpMenu from './user-op-menu';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemFreezed: false
|
||||
};
|
||||
}
|
||||
|
||||
onFreezedItem = () => {
|
||||
this.setState({isItemFreezed: true});
|
||||
}
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({isItemFreezed: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -19,38 +34,39 @@ class Content extends Component {
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('This user has not created or joined any groups')}</h2>
|
||||
<h2>{gettext('No groups')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%">{gettext('Name')}</th>
|
||||
<th width="35%">{gettext('Name')}</th>
|
||||
<th width="30%">{gettext('Role')}</th>
|
||||
<th width="30%">{gettext('Create At')}</th>
|
||||
<th width="10%">{gettext('Operations')}</th>
|
||||
<th width="30%">{gettext('Created At')}</th>
|
||||
<th width="5%">{/* Operations */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
deleteGroup={this.props.deleteGroup}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
deleteItem={this.props.deleteItem}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
@@ -61,60 +77,116 @@ class Item extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
isDeleteDialogOpen: false,
|
||||
isOpIconShown: false,
|
||||
highlight: false,
|
||||
isDeleteDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({showOpIcon: true});
|
||||
handleMouseEnter = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShown: true,
|
||||
highlight: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({showOpIcon: false});
|
||||
handleMouseLeave = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShown: false,
|
||||
highlight: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({
|
||||
highlight: false,
|
||||
isOpIconShow: false
|
||||
});
|
||||
this.props.onUnfreezedItem();
|
||||
}
|
||||
|
||||
toggleDeleteDialog = () => {
|
||||
this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen});
|
||||
}
|
||||
|
||||
deleteGroup = () => {
|
||||
this.props.deleteGroup(this.props.item.id);
|
||||
this.toggleDeleteDialog();
|
||||
deleteItem = () => {
|
||||
this.props.deleteItem(this.props.item.id);
|
||||
}
|
||||
|
||||
translateOperations = (item) => {
|
||||
let translateResult = '';
|
||||
switch (item) {
|
||||
case 'Delete':
|
||||
translateResult = gettext('Delete');
|
||||
break;
|
||||
}
|
||||
|
||||
return translateResult;
|
||||
}
|
||||
|
||||
onMenuItemClick = (operation) => {
|
||||
switch(operation) {
|
||||
case 'Delete':
|
||||
this.toggleDeleteDialog();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getRoleText = () => {
|
||||
let roleText;
|
||||
const { item } = this.props;
|
||||
switch(item.role) {
|
||||
case 'Owner':
|
||||
roleText = gettext('Owner');
|
||||
break;
|
||||
case 'Admin':
|
||||
roleText = gettext('Admin');
|
||||
break;
|
||||
case 'Member':
|
||||
roleText = gettext('Member');
|
||||
break;
|
||||
}
|
||||
return roleText;
|
||||
}
|
||||
|
||||
render() {
|
||||
let { showOpIcon, isDeleteDialogOpen } = this.state;
|
||||
let { item } = this.props;
|
||||
let roleText;
|
||||
if (item.role == 'owner') {
|
||||
roleText = gettext('Owner');
|
||||
} else if (item.role == 'admin') {
|
||||
roleText = gettext('Admin');
|
||||
} else if (item.role == 'member') {
|
||||
roleText = gettext('Member');
|
||||
}
|
||||
const { item } = this.props;
|
||||
const { isOpIconShown, isDeleteDialogOpen } = this.state;
|
||||
|
||||
let groupName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
||||
let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?'.replace('{placeholder}', groupName))
|
||||
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
||||
const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
||||
|
||||
const url = item.parent_group_id == 0 ?
|
||||
`${siteRoot}sys/groups/${item.id}/libraries/` :
|
||||
`${siteRoot}sys/departments/${item.id}/`;
|
||||
|
||||
let iconVisibility = showOpIcon ? '' : ' invisible';
|
||||
let deleteIconClassName = 'op-icon sf2-icon-delete' + iconVisibility;
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td><a href={siteRoot + 'sysadmin/#groups/' + item.id + '/libs/'}>{item.name}</a></td>
|
||||
<td>{roleText}</td>
|
||||
<td>{moment(item.created_at).fromNow()}</td>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<td><Link to={url}>{item.name}</Link></td>
|
||||
<td>{this.getRoleText()}</td>
|
||||
<td>{moment(item.created_at).format('YYYY-MM-DD HH:mm')}</td>
|
||||
<td>
|
||||
<a href="#" className={deleteIconClassName} title={gettext('Delete')} onClick={this.toggleDeleteDialog}></a>
|
||||
{(isOpIconShown && item.parent_group_id == 0) &&
|
||||
<OpMenu
|
||||
operations={['Delete']}
|
||||
translateOperations={this.translateOperations}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
onFreezedItem={this.props.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
/>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{isDeleteDialogOpen &&
|
||||
<CommonOperationConfirmationDialog
|
||||
title={gettext('Delete Library')}
|
||||
<CommonOperationConfirmationDialog
|
||||
title={gettext('Delete Group')}
|
||||
message={deleteDialogMsg}
|
||||
executeOperation={this.deleteGroup}
|
||||
executeOperation={this.deleteItem}
|
||||
confirmBtnText={gettext('Delete')}
|
||||
toggleDialog={this.toggleDeleteDialog}
|
||||
/>
|
||||
@@ -124,22 +196,28 @@ class Item extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
class UserGroups extends Component {
|
||||
class Groups extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
groupList: [],
|
||||
userInfo: {},
|
||||
items: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
seafileAPI.sysAdminListAllGroupsJoinedByUser(this.props.email).then(res => {
|
||||
seafileAPI.sysAdminGetUser(this.props.email).then((res) => {
|
||||
this.setState({
|
||||
groupList: res.data.group_list,
|
||||
loading: false
|
||||
userInfo: res.data
|
||||
});
|
||||
});
|
||||
seafileAPI.sysAdminListGroupsJoinedByUser(this.props.email).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
items: res.data.group_list
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
@@ -147,12 +225,13 @@ class UserGroups extends Component {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
@@ -163,14 +242,13 @@ class UserGroups extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
deleteGroup = (groupID) => {
|
||||
deleteItem = (groupID) => {
|
||||
seafileAPI.sysAdminDismissGroupByID(groupID).then(res => {
|
||||
let newGroupList = this.state.groupList.filter(item=> {
|
||||
let items = this.state.items.filter(item => {
|
||||
return item.id != groupID;
|
||||
});
|
||||
this.setState({
|
||||
groupList: newGroupList
|
||||
});
|
||||
this.setState({items: items});
|
||||
toaster.success(gettext('Successfully deleted 1 item.'));
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
@@ -179,16 +257,24 @@ class UserGroups extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.groupList}
|
||||
deleteGroup={this.deleteGroup}
|
||||
/>
|
||||
</div>
|
||||
<Fragment>
|
||||
<MainPanelTopbar />
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<Nav currentItem="groups" email={this.props.email} userName={this.state.userInfo.name} />
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.items}
|
||||
deleteItem={this.deleteItem}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserGroups;
|
||||
export default Groups;
|
||||
|
@@ -1,32 +1,199 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Nav, NavItem, NavLink, TabContent, TabPane, Label } from 'reactstrap';
|
||||
import { gettext, siteRoot } from '../../../utils/constants';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import classnames from 'classnames';
|
||||
import toaster from '../../../components/toast';
|
||||
import { FormGroup, Label, Input, Button } from 'reactstrap';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import MainPanelTopbar from '../../org-admin/main-panel-topbar';
|
||||
import UserProfile from './user-profile';
|
||||
import UserOwnedRepos from './user-owned-repos';
|
||||
import UserSharedInRepos from './user-share-in-repos';
|
||||
import UserShareLinks from './user-share-links';
|
||||
import UserGroups from './user-groups';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { loginUrl, gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import Loading from '../../../components/loading';
|
||||
import SysAdminSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota';
|
||||
import SysAdminUpdateUserDialog from '../../../components/dialog/sysadmin-dialog/update-user';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import Nav from './user-nav';
|
||||
|
||||
class UserInfo extends Component {
|
||||
const { twoFactorAuthEnabled } = window.sysadmin.pageOptions;
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeTab: 'profile',
|
||||
currentKey: '',
|
||||
dialogTitle: '',
|
||||
isSetQuotaDialogOpen: false,
|
||||
isUpdateUserDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
toggleSetQuotaDialog = () => {
|
||||
this.setState({isSetQuotaDialogOpen: !this.state.isSetQuotaDialogOpen});
|
||||
}
|
||||
|
||||
updateQuota = (value) => {
|
||||
this.props.updateUser('quota_total', value);
|
||||
}
|
||||
|
||||
toggleDialog = (key, dialogTitle) => {
|
||||
this.setState({
|
||||
currentKey: key,
|
||||
dialogTitle: dialogTitle,
|
||||
isUpdateUserDialogOpen: !this.state.isUpdateUserDialogOpen
|
||||
});
|
||||
}
|
||||
|
||||
toggleSetNameDialog = () => {
|
||||
this.toggleDialog('name', gettext('Set Name'));
|
||||
}
|
||||
|
||||
toggleSetUserLoginIDDialog = () => {
|
||||
this.toggleDialog('login_id', gettext('Set Login ID'));
|
||||
}
|
||||
|
||||
toggleSetUserComtactEmailDialog = () => {
|
||||
this.toggleDialog('contact_email', gettext('Set Contact Email'));
|
||||
}
|
||||
|
||||
toggleSetUserReferenceIDDialog = () => {
|
||||
this.toggleDialog('reference_id', gettext('Set Reference ID'));
|
||||
}
|
||||
|
||||
updateValue = (value) => {
|
||||
this.props.updateUser(this.state.currentKey, value);
|
||||
}
|
||||
|
||||
toggleUpdateUserDialog = () => {
|
||||
this.toggleDialog('', '');
|
||||
}
|
||||
|
||||
showEditIcon = (action) => {
|
||||
return (
|
||||
<span
|
||||
title={gettext('Edit')}
|
||||
className="fa fa-pencil-alt attr-action-icon"
|
||||
onClick={action}>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, userInfo } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
} else {
|
||||
const user = this.props.userInfo;
|
||||
const {
|
||||
currentKey, dialogTitle,
|
||||
isSetQuotaDialogOpen, isUpdateUserDialogOpen
|
||||
} = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<dl className="m-0">
|
||||
<dt className="info-item-heading">{gettext('Avatar')}</dt>
|
||||
<dd className="info-item-content">
|
||||
<img src={user.avatar_url} alt={user.name} width="80" className="rounded" />
|
||||
</dd>
|
||||
|
||||
<dt className="info-item-heading">{gettext('Email')}</dt>
|
||||
<dd className="info-item-content">{user.email}</dd>
|
||||
|
||||
{user.org_name &&
|
||||
<Fragment>
|
||||
<dt className="info-item-heading">{gettext('Organization')}</dt>
|
||||
<dd className="info-item-content">{user.org_name}</dd>
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
<dt className="info-item-heading">{gettext('Name')}</dt>
|
||||
<dd className="info-item-content">
|
||||
{user.name || '--'}
|
||||
{this.showEditIcon(this.toggleSetNameDialog)}
|
||||
</dd>
|
||||
|
||||
<dt className="info-item-heading">{gettext('Login ID')}</dt>
|
||||
<dd className="info-item-content">
|
||||
{user.login_id || '--'}
|
||||
{this.showEditIcon(this.toggleSetUserLoginIDDialog)}
|
||||
</dd>
|
||||
|
||||
<dt className="info-item-heading">{gettext('Contact Email')}</dt>
|
||||
<dd className="info-item-content">
|
||||
{user.contact_email || '--'}
|
||||
{this.showEditIcon(this.toggleSetUserComtactEmailDialog)}
|
||||
</dd>
|
||||
|
||||
<dt className="info-item-heading">{gettext('Reference ID')}</dt>
|
||||
<dd className="info-item-content">
|
||||
{user.reference_id|| '--'}
|
||||
{this.showEditIcon(this.toggleSetUserReferenceIDDialog)}
|
||||
</dd>
|
||||
|
||||
<dt className="info-item-heading">{gettext('Space Used / Quota')}</dt>
|
||||
<dd className="info-item-content">
|
||||
{`${Utils.bytesToSize(user.quota_usage)} / ${user.quota_total > 0 ? Utils.bytesToSize(user.quota_total) : '--'}`}
|
||||
{this.showEditIcon(this.toggleSetQuotaDialog)}
|
||||
</dd>
|
||||
|
||||
{twoFactorAuthEnabled &&
|
||||
<Fragment>
|
||||
<dt className="info-item-heading">{gettext('Two-Factor Authentication')}</dt>
|
||||
<dd className="info-item-content">
|
||||
{user.has_default_device ?
|
||||
<FormGroup>
|
||||
<p className="mb-1">{gettext('Status: enabled')}</p>
|
||||
<Button onClick={this.props.disable2FA}>{gettext('Disable Two-Factor Authentication')}</Button>
|
||||
</FormGroup> :
|
||||
<FormGroup>
|
||||
<Button disabled={true}>{gettext('Disable Two-Factor Authentication')}</Button>
|
||||
</FormGroup>
|
||||
}
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<Input type="checkbox" checked={user.is_force_2fa} onChange={this.props.toggleForce2fa} />
|
||||
<span>{gettext('Force Two-Factor Authentication')}</span>
|
||||
</Label>
|
||||
</FormGroup>
|
||||
</dd>
|
||||
</Fragment>
|
||||
}
|
||||
</dl>
|
||||
{isSetQuotaDialogOpen &&
|
||||
<SysAdminSetQuotaDialog
|
||||
updateQuota={this.updateQuota}
|
||||
toggle={this.toggleSetQuotaDialog}
|
||||
/>
|
||||
}
|
||||
{isUpdateUserDialogOpen &&
|
||||
<SysAdminUpdateUserDialog
|
||||
dialogTitle={dialogTitle}
|
||||
value={user[currentKey]}
|
||||
updateValue={this.updateValue}
|
||||
toggleDialog={this.toggleUpdateUserDialog}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class User extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
userInfo: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.sysAdminGetUserInfo(this.props.email).then(res => {
|
||||
componentDidMount () {
|
||||
// avatar size: 160
|
||||
seafileAPI.sysAdminGetUser(this.props.email, 160).then((res) => {
|
||||
this.setState({
|
||||
userInfo: res.data,
|
||||
loading: false
|
||||
loading: false,
|
||||
userInfo: res.data
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
@@ -34,13 +201,14 @@ class UserInfo extends Component {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
@@ -50,87 +218,44 @@ class UserInfo extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
toggle(tab) {
|
||||
if (this.state.activeTab !== tab) {
|
||||
this.setState({
|
||||
activeTab: tab
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onNameChanged = (name) => {
|
||||
seafileAPI.sysAdminUpdateUserInfo('name', name, this.props.email).then(res => {
|
||||
this.setState({
|
||||
userInfo: res.data,
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
onLoginIDChanged = (loginID) => {
|
||||
seafileAPI.sysAdminUpdateUserInfo('login_id', loginID, this.props.email).then(res => {
|
||||
this.setState({
|
||||
userInfo: res.data,
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onContactEmailChanged = (contactEmail) => {
|
||||
seafileAPI.sysAdminUpdateUserInfo('contact_email', contactEmail, this.props.email).then(res => {
|
||||
this.setState({
|
||||
userInfo: res.data,
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
onReferenceIDChanged = (referenceID) => {
|
||||
seafileAPI.sysAdminUpdateUserInfo('reference_id', referenceID, this.props.email).then(res => {
|
||||
this.setState({
|
||||
userInfo: res.data,
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
onQuotaChanged = (quota) => {
|
||||
seafileAPI.sysAdminUpdateUserInfo('quota_total', quota, this.props.email).then(res => {
|
||||
this.setState({
|
||||
userInfo: res.data,
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
toggleForce2FA = (isForce2FA) => {
|
||||
seafileAPI.sysAdminToggleForceTwoFactorAuth(isForce2FA, this.props.email).then(res => {
|
||||
updateUser = (key, value) => {
|
||||
const email = this.state.userInfo.email;
|
||||
seafileAPI.sysAdminUpdateUser(email, key, value).then(res => {
|
||||
let userInfo = this.state.userInfo;
|
||||
userInfo.is_force_2fa = isForce2FA;
|
||||
this.setState({userInfo: userInfo});
|
||||
userInfo[key]= res.data[key];
|
||||
this.setState({
|
||||
userInfo: userInfo
|
||||
});
|
||||
toaster.success(gettext('Edit succeeded'));
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteVerified2FADevices = () => {
|
||||
seafileAPI.sysAdminDeleteVerifiedTwoFactorAuth(this.props.email).then(res => {
|
||||
disable2FA = () => {
|
||||
const email = this.state.userInfo.email;
|
||||
seafileAPI.sysAdminDeleteTwoFactorAuth(email).then(res => {
|
||||
let userInfo = this.state.userInfo;
|
||||
userInfo.has_default_device = false;
|
||||
this.setState({userInfo: userInfo});
|
||||
toaster.success(gettext('success'));
|
||||
this.setState({
|
||||
userInfo: userInfo
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
toggleForce2fa = (e) => {
|
||||
const email = this.state.userInfo.email;
|
||||
const checked = e.target.checked;
|
||||
seafileAPI.sysAdminSetForceTwoFactorAuth(email, checked).then(res => {
|
||||
let userInfo = this.state.userInfo;
|
||||
userInfo.is_force_2fa = checked;
|
||||
this.setState({
|
||||
userInfo: userInfo
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
@@ -138,102 +263,22 @@ class UserInfo extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { userInfo } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar>
|
||||
<Fragment>
|
||||
<a href={siteRoot + 'sys/users-all/'}>{gettext('Users')}</a>
|
||||
{' / '}
|
||||
<Label>{this.state.userInfo.name}</Label>
|
||||
</Fragment>
|
||||
</MainPanelTopbar>
|
||||
<MainPanelTopbar />
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path align-items-center">
|
||||
<Nav>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'profile' })}
|
||||
onClick={() => { this.toggle('profile'); }}>
|
||||
{gettext('Profile')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'ownedLibs' })}
|
||||
onClick={() => { this.toggle('ownedLibs'); }}>
|
||||
{gettext('Owned Libs')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'sharedLibs' })}
|
||||
onClick={() => { this.toggle('sharedLibs'); }}>
|
||||
{gettext('Shared Libs')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'sharedLinks' })}
|
||||
onClick={() => { this.toggle('sharedLinks'); }}>
|
||||
{gettext('Shared Links')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink
|
||||
className={classnames({ active: this.state.activeTab === 'groups' })}
|
||||
onClick={() => { this.toggle('groups'); }}>
|
||||
{gettext('Groups')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</div>
|
||||
<Nav currentItem="info" email={this.props.email} userName={userInfo.name} />
|
||||
<div className="cur-view-content">
|
||||
<TabContent activeTab={this.state.activeTab}>
|
||||
<TabPane tabId="profile">
|
||||
{this.state.activeTab === 'profile' &&
|
||||
<UserProfile
|
||||
email={this.props.email}
|
||||
userInfo={this.state.userInfo}
|
||||
onNameChanged={this.onNameChanged}
|
||||
onContactEmailChanged={this.onContactEmailChanged}
|
||||
onLoginIDChanged={this.onLoginIDChanged}
|
||||
onReferenceIDChanged={this.onReferenceIDChanged}
|
||||
onQuotaChanged={this.onQuotaChanged}
|
||||
toggleForce2FA={this.toggleForce2FA}
|
||||
deleteVerified2FADevices={this.deleteVerified2FADevices}
|
||||
/>
|
||||
}
|
||||
</TabPane>
|
||||
<TabPane tabId="ownedLibs">
|
||||
{this.state.activeTab === 'ownedLibs' &&
|
||||
<UserOwnedRepos
|
||||
email={this.props.email}
|
||||
/>
|
||||
}
|
||||
</TabPane>
|
||||
<TabPane tabId="sharedLibs">
|
||||
{this.state.activeTab === 'sharedLibs' &&
|
||||
<UserSharedInRepos
|
||||
email={this.props.email}
|
||||
/>
|
||||
}
|
||||
</TabPane>
|
||||
<TabPane tabId="sharedLinks">
|
||||
{this.state.activeTab === 'sharedLinks' &&
|
||||
<UserShareLinks
|
||||
email={this.props.email}
|
||||
/>
|
||||
}
|
||||
</TabPane>
|
||||
<TabPane tabId="groups">
|
||||
{this.state.activeTab === 'groups' &&
|
||||
<UserGroups
|
||||
email={this.props.email}
|
||||
/>
|
||||
}
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
userInfo={this.state.userInfo}
|
||||
updateUser={this.updateUser}
|
||||
disable2FA={this.disable2FA}
|
||||
toggleForce2fa={this.toggleForce2fa}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,4 +287,4 @@ class UserInfo extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default UserInfo;
|
||||
export default User;
|
||||
|
340
frontend/src/pages/sys-admin/users/user-links.js
Normal file
340
frontend/src/pages/sys-admin/users/user-links.js
Normal file
@@ -0,0 +1,340 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { loginUrl, gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import Loading from '../../../components/loading';
|
||||
import LinkDialog from '../../../components/dialog/share-admin-link';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import Nav from './user-nav';
|
||||
import OpMenu from './user-op-menu';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemFreezed: false
|
||||
};
|
||||
}
|
||||
|
||||
onFreezedItem = () => {
|
||||
this.setState({isItemFreezed: true});
|
||||
}
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({isItemFreezed: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No shared links')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">{/* icon */}</th>
|
||||
<th width="30%">{gettext('Name')}</th>
|
||||
<th width="20%">{gettext('Size')}</th>
|
||||
<th width="20%">{gettext('Type')}</th>
|
||||
<th width="20%">{gettext('Visits')}</th>
|
||||
<th width="5%">{/* Operations */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
deleteItem={this.props.deleteItem}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShown: false,
|
||||
highlight: false,
|
||||
isLinkDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseEnter = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShown: true,
|
||||
highlight: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseLeave = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShown: false,
|
||||
highlight: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({
|
||||
highlight: false,
|
||||
isOpIconShow: false
|
||||
});
|
||||
this.props.onUnfreezedItem();
|
||||
}
|
||||
|
||||
toggleLinkDialog = () => {
|
||||
this.setState({isLinkDialogOpen: !this.state.isLinkDialogOpen});
|
||||
}
|
||||
|
||||
deleteItem = () => {
|
||||
this.props.deleteItem(this.props.item);
|
||||
}
|
||||
|
||||
translateOperations = (item) => {
|
||||
let translateResult = '';
|
||||
switch (item) {
|
||||
case 'View':
|
||||
translateResult = gettext('View');
|
||||
break;
|
||||
case 'Delete':
|
||||
translateResult = gettext('Delete');
|
||||
break;
|
||||
}
|
||||
|
||||
return translateResult;
|
||||
}
|
||||
|
||||
onMenuItemClick = (operation) => {
|
||||
switch(operation) {
|
||||
case 'View':
|
||||
this.toggleLinkDialog();
|
||||
break;
|
||||
case 'Delete':
|
||||
this.deleteItem();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getRoleText = () => {
|
||||
let roleText;
|
||||
const { item } = this.props;
|
||||
switch(item.role) {
|
||||
case 'Owner':
|
||||
roleText = gettext('Owner');
|
||||
break;
|
||||
case 'Admin':
|
||||
roleText = gettext('Admin');
|
||||
break;
|
||||
case 'Member':
|
||||
roleText = gettext('Member');
|
||||
break;
|
||||
}
|
||||
return roleText;
|
||||
}
|
||||
|
||||
getIconUrl = () => {
|
||||
const { item } = this.props;
|
||||
let url;
|
||||
if (item.type == 'upload') {
|
||||
url = Utils.getFolderIconUrl();
|
||||
} else { // share link
|
||||
if (item.is_dir) {
|
||||
url = Utils.getFolderIconUrl();
|
||||
} else {
|
||||
url = Utils.getFileIconUrl(item.obj_name);
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { item } = this.props;
|
||||
const { isOpIconShown, isLinkDialogOpen } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<td><img src={this.getIconUrl()} alt="" width="24" /></td>
|
||||
<td>{item.obj_name == '/' ? item.repo_name : item.obj_name}</td>
|
||||
{item.type == 'upload' ?
|
||||
<Fragment>
|
||||
<td></td>
|
||||
<td>{gettext('Upload')}</td>
|
||||
</Fragment> :
|
||||
<Fragment>
|
||||
<td>{item.is_dir ? null : Utils.bytesToSize(item.size)}</td>
|
||||
<td>{gettext('Download')}</td>
|
||||
</Fragment>
|
||||
}
|
||||
<td>{item.view_cnt}</td>
|
||||
<td>
|
||||
{isOpIconShown &&
|
||||
<OpMenu
|
||||
operations={['View', 'Delete']}
|
||||
translateOperations={this.translateOperations}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
onFreezedItem={this.props.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
/>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{isLinkDialogOpen &&
|
||||
<LinkDialog
|
||||
link={item.link}
|
||||
toggleDialog={this.toggleLinkDialog}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Links extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
userInfo: {},
|
||||
uploadLinkItems: [],
|
||||
shareLinkItems: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
seafileAPI.sysAdminGetUser(this.props.email).then((res) => {
|
||||
this.setState({
|
||||
userInfo: res.data
|
||||
});
|
||||
});
|
||||
|
||||
seafileAPI.sysAdminListShareLinksByUser(this.props.email).then(res => {
|
||||
const items = res.data.share_link_list.map(item => {
|
||||
item.type = 'download';
|
||||
return item;
|
||||
});
|
||||
items.sort((a, b) => {
|
||||
return a.is_dir ? -1 : 1;
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
shareLinkItems: items
|
||||
});
|
||||
});
|
||||
seafileAPI.sysAdminListUploadLinksByUser(this.props.email).then(res => {
|
||||
const items = res.data.upload_link_list.map(item => {
|
||||
item.type = 'upload';
|
||||
return item;
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
uploadLinkItems: items
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteItem = (item) => {
|
||||
const type = item.type;
|
||||
const token = item.token;
|
||||
if (type == 'download') {
|
||||
seafileAPI.sysAdminDeleteShareLink(token).then(res => {
|
||||
let items = this.state.shareLinkItems.filter(item=> {
|
||||
return item.token != token;
|
||||
});
|
||||
this.setState({
|
||||
shareLinkItems: items
|
||||
});
|
||||
toaster.success(gettext('Successfully deleted 1 item.'));
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
} else {
|
||||
seafileAPI.sysAdminDeleteUploadLink(token).then(res => {
|
||||
let items = this.state.uploadLinkItems.filter(item=> {
|
||||
return item.token != token;
|
||||
});
|
||||
this.setState({
|
||||
uploadLinkItems: items
|
||||
});
|
||||
toaster.success(gettext('Successfully deleted 1 item.'));
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { shareLinkItems, uploadLinkItems } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar />
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<Nav currentItem="links" email={this.props.email} userName={this.state.userInfo.name} />
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={[].concat(uploadLinkItems, shareLinkItems)}
|
||||
deleteItem={this.deleteItem}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Links;
|
46
frontend/src/pages/sys-admin/users/user-nav.js
Normal file
46
frontend/src/pages/sys-admin/users/user-nav.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import { siteRoot, gettext } from '../../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
currentItem: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
class Nav extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.navItems = [
|
||||
{name: 'info', urlPart: '', text: gettext('Info')},
|
||||
{name: 'owned-repos', urlPart: 'owned-libraries', text: gettext('Owned Libraries')},
|
||||
{name: 'shared-repos', urlPart: 'shared-libraries', text: gettext('Shared Libraries')},
|
||||
{name: 'links', urlPart: 'shared-links', text: gettext('Shared Links')},
|
||||
{name: 'groups', urlPart: 'groups', text: gettext('Groups')}
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currentItem, email, userName } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading"><Link to={`${siteRoot}sys/users/`}>{gettext('Users')}</Link> / {userName}</h3>
|
||||
</div>
|
||||
<ul className="nav border-bottom mx-4">
|
||||
{this.navItems.map((item, index) => {
|
||||
return (
|
||||
<li className="nav-item mr-2" key={index}>
|
||||
<Link to={`${siteRoot}sys/users/${encodeURIComponent(email)}/${item.urlPart}`} className={`nav-link ${currentItem == item.name ? ' active' : ''}`}>{item.text}</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Nav.propTypes = propTypes;
|
||||
|
||||
export default Nav;
|
@@ -1,236 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { username } from '../../../utils/constants';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import moment from 'moment';
|
||||
import Loading from '../../../components/loading';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
import SysAdminRepoTransferDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-repo-transfer-dialog';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('This user has not created any libraries')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">{/*icon*/}</th>
|
||||
<th width="20%">{gettext('Name')}</th>
|
||||
<th width="25%">{gettext('Size')}</th>
|
||||
<th width="19%">{gettext('Last Update')}</th>
|
||||
<th width="10%">{/*Operations*/}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
deleteRepo={this.props.deleteRepo}
|
||||
transferRepo={this.props.transferRepo}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
role: this.props.item.role,
|
||||
quota_total: this.props.item.quota_total,
|
||||
isDeleteDialogOpen: false,
|
||||
isTransferDialogOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({showOpIcon: true});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({showOpIcon: false});
|
||||
}
|
||||
|
||||
toggleDeleteDialog = () => {
|
||||
this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen});
|
||||
}
|
||||
|
||||
toggleTransferDialog = () => {
|
||||
this.setState({isTransferDialogOpen: !this.state.isTransferDialogOpen});
|
||||
}
|
||||
|
||||
deleteRepo = () => {
|
||||
this.props.deleteRepo(this.props.item.id);
|
||||
this.toggleDeleteDialog();
|
||||
}
|
||||
|
||||
transferRepo = (receiver) => {
|
||||
this.props.transferRepo(receiver.email, this.props.item.id);
|
||||
this.toggleTransferDialog();
|
||||
}
|
||||
|
||||
render() {
|
||||
let { showOpIcon, isDeleteDialogOpen, isTransferDialogOpen } = this.state;
|
||||
let { item } = this.props;
|
||||
|
||||
let iconUrl = Utils.getLibIconUrl(item);
|
||||
let iconTitle = Utils.getLibIconTitle(item);
|
||||
|
||||
let repoName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
||||
let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?'.replace('{placeholder}', repoName))
|
||||
|
||||
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
||||
let deleteIconClassName = 'op-icon sf2-icon-delete' + iconVisibility;
|
||||
let transferIconClassName = 'op-icon sf2-icon-move' + iconVisibility;
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||
<td>{item.name}</td>
|
||||
<td>{Utils.bytesToSize(item.size)}</td>
|
||||
<td>{moment(item.last_modify).fromNow()}</td>
|
||||
<td>
|
||||
{item.email != username && showOpIcon &&
|
||||
<Fragment>
|
||||
<a href="#" className={deleteIconClassName} title={gettext('Delete')} onClick={this.toggleDeleteDialog}></a>
|
||||
<a href="#" className={transferIconClassName} title={gettext('Transfer')} onClick={this.toggleTransferDialog}></a>
|
||||
</Fragment>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{isDeleteDialogOpen &&
|
||||
<CommonOperationConfirmationDialog
|
||||
title={gettext('Delete Library')}
|
||||
message={deleteDialogMsg}
|
||||
executeOperation={this.deleteRepo}
|
||||
confirmBtnText={gettext('Delete')}
|
||||
toggleDialog={this.toggleDeleteDialog}
|
||||
/>
|
||||
}
|
||||
{isTransferDialogOpen &&
|
||||
<SysAdminRepoTransferDialog
|
||||
repoName={item.name}
|
||||
toggle={this.toggleTransferDialog}
|
||||
submit={this.transferRepo}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserOwnedRepos extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
repoList: [],
|
||||
isShowImportWaitingDialog: false,
|
||||
isShowAddUserWaitingDialog: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
seafileAPI.sysAdminListAllRepoInfoByOwner(this.props.email).then(res => {
|
||||
this.setState({
|
||||
repoList: res.data.repos,
|
||||
loading: false
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteRepo = (repoID) => {
|
||||
seafileAPI.sysAdminDeleteRepo(repoID).then(res => {
|
||||
let newRepoList = this.state.repoList.filter(repo => {
|
||||
return repo.id != repoID;
|
||||
});
|
||||
this.setState({
|
||||
repoList: newRepoList
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
transferRepo = (receiverEmail, repoID) => {
|
||||
seafileAPI.sysAdminTransferRepo(repoID, receiverEmail).then(res => {
|
||||
let newRepoList = this.state.repoList.filter(repo => {
|
||||
return repo.id != repoID;
|
||||
});
|
||||
this.setState({
|
||||
repoList: newRepoList
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.repoList}
|
||||
deleteRepo={this.deleteRepo}
|
||||
transferRepo={this.transferRepo}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserOwnedRepos;
|
@@ -1,188 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Input, Button } from 'reactstrap';
|
||||
import { gettext, enableTwoFactorAuth } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import PropTypes from 'prop-types';
|
||||
import Loading from '../../../components/loading';
|
||||
import SysAdminUserSetNameDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-set-name-dialog';
|
||||
import SysAdminUserSetContactEmailDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-set-contact-email-dialog';
|
||||
import SysAdminUserSetLoginIDDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-set-loginid-dialog';
|
||||
import SysAdminUserSetReferenceIDDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-set-referenceid-dialog';
|
||||
import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-set-quota-dialog';
|
||||
import '../../../css/system-info.css';
|
||||
|
||||
const { avatarURL } = window.app.config;
|
||||
|
||||
const propTypes = {
|
||||
email: PropTypes.string.isRequired,
|
||||
userInfo: PropTypes.object.isRequired,
|
||||
onNameChanged: PropTypes.func.isRequired,
|
||||
onContactEmailChanged: PropTypes.func.isRequired,
|
||||
onLoginIDChanged: PropTypes.func.isRequired,
|
||||
onReferenceIDChanged: PropTypes.func.isRequired,
|
||||
onQuotaChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class UserProfile extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
userInfo: {},
|
||||
isSetUserNameDialogOpen: false,
|
||||
isSetUserLoginIDDialogOpen: false,
|
||||
isSetUserContactEmailDialogOpen: false,
|
||||
isSetUserReferenceIDDialogOpen: false,
|
||||
isSetUserQuotaDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
toggleSetUserNameDialog = () => {
|
||||
this.setState({isSetUserNameDialogOpen: !this.state.isSetUserNameDialogOpen});
|
||||
}
|
||||
|
||||
toggleSetUserLoginIDDialog = () => {
|
||||
this.setState({isSetUserLoginIDDialogOpen: !this.state.isSetUserLoginIDDialogOpen});
|
||||
}
|
||||
|
||||
toggleSetUserContactEmailDialog = () => {
|
||||
this.setState({isSetUserContactEmailDialogOpen: !this.state.isSetUserContactEmailDialogOpen});
|
||||
}
|
||||
|
||||
toggleSetUserReferenceIDDialog = () => {
|
||||
this.setState({isSetUserReferenceIDDialogOpen: !this.state.isSetUserReferenceIDDialogOpen});
|
||||
}
|
||||
|
||||
toggleSetUserQuotaDialog = () => {
|
||||
this.setState({isSetUserQuotaDialogOpen: !this.state.isSetUserQuotaDialogOpen});
|
||||
}
|
||||
|
||||
onNameChanged = (name) => {
|
||||
this.props.onNameChanged(name);
|
||||
this.toggleSetUserNameDialog();
|
||||
}
|
||||
|
||||
onLoginIDChanged = (loginID) => {
|
||||
this.props.onLoginIDChanged(loginID);
|
||||
this.toggleSetUserLoginIDDialog();
|
||||
}
|
||||
|
||||
onContactEmailChanged = (contactEmail) => {
|
||||
this.props.onContactEmailChanged(contactEmail);
|
||||
this.toggleSetUserContactEmailDialog();
|
||||
}
|
||||
|
||||
onReferenceIDChanged = (referenceID) => {
|
||||
this.props.onReferenceIDChanged(referenceID);
|
||||
this.toggleSetUserReferenceIDDialog();
|
||||
}
|
||||
|
||||
onQuotaChanged = (quota) => {
|
||||
this.props.onQuotaChanged(quota);
|
||||
this.toggleSetUserQuotaDialog();
|
||||
}
|
||||
|
||||
toggleForce2FA = (is_force_2fa) => {
|
||||
this.props.toggleForce2FA(is_force_2fa);
|
||||
}
|
||||
|
||||
deleteVerified2FADevices = () => {
|
||||
this.props.deleteVerified2FADevices();
|
||||
}
|
||||
|
||||
render() {
|
||||
let { errorMsg, isSetUserContactEmailDialogOpen, isSetUserLoginIDDialogOpen,
|
||||
isSetUserNameDialogOpen, isSetUserQuotaDialogOpen, isSetUserReferenceIDDialogOpen } = this.state;
|
||||
let { email, name, login_id, contact_email, reference_id, quota_usage, quota_total, is_force_2fa,
|
||||
has_default_device } = this.props.userInfo;
|
||||
|
||||
if (!this.props.userInfo) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="info-item">
|
||||
<h4 className="info-item-heading">{gettext('Avatar')}</h4>
|
||||
<img src={avatarURL} width="80" height="80" alt="" className="user-avatar" />
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<h4 className="info-item-heading">{gettext('Email')}</h4>
|
||||
<span>{email}</span>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<h4 className="info-item-heading">{gettext('Name')}</h4>
|
||||
<span>{name ? name : '--'}</span>
|
||||
<span onClick={this.toggleSetUserNameDialog} title={gettext('Edit')} style={{wdith:'14px', height:'14px'}} className="fa fa-pencil-alt attr-action-icon"></span>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<h4 className="info-item-heading">{gettext('Login ID')}</h4>
|
||||
<span>{login_id ? login_id : '--'}</span>
|
||||
<span onClick={this.toggleSetUserLoginIDDialog} title={gettext('Edit')} style={{wdith:'14px', height:'14px'}} className="fa fa-pencil-alt attr-action-icon"></span>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<h4 className="info-item-heading">{gettext('Contact Email')}</h4>
|
||||
<span>{contact_email ? contact_email : '--'}</span>
|
||||
<span onClick={this.toggleSetUserContactEmailDialog} title={gettext('Edit')} style={{wdith:'14px', height:'14px'}} className="fa fa-pencil-alt attr-action-icon"></span>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<h4 className="info-item-heading">{gettext('Reference ID')}</h4>
|
||||
<span>{reference_id ? reference_id : '--'}</span>
|
||||
<span onClick={this.toggleSetUserReferenceIDDialog} title={gettext('Edit')} style={{wdith:'14px', height:'14px'}} className="fa fa-pencil-alt attr-action-icon"></span>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<h4 className="info-item-heading">{gettext('Space Used')}{' / '}{gettext('Quota')}</h4>
|
||||
{Utils.bytesToSize(quota_usage)}{' / '}
|
||||
{quota_total >= 0 ? Utils.bytesToSize(quota_total) : '--'}
|
||||
<span onClick={this.toggleSetUserQuotaDialog} title={gettext('Edit Quota')} style={{wdith:'14px', height:'14px'}} className="fa fa-pencil-alt attr-action-icon"></span>
|
||||
</div>
|
||||
{enableTwoFactorAuth &&
|
||||
<div className="info-item">
|
||||
<h4 className="info-item-heading">{gettext('Two-Factor Authentication')}</h4>
|
||||
<Button color="primary" disabled={!has_default_device} onClick={this.deleteVerified2FADevices}>{gettext('Disable Two-Factor Authentication')}</Button>
|
||||
<br/>
|
||||
<label><Input className="ml-0" type="checkbox" onChange={() => {this.toggleForce2FA(!is_force_2fa)}} checked={is_force_2fa ? true : false} />{gettext('Force Two-Factor Authentication')}</label>
|
||||
</div>
|
||||
}
|
||||
{isSetUserNameDialogOpen &&
|
||||
<SysAdminUserSetNameDialog
|
||||
toggle={this.toggleSetUserNameDialog}
|
||||
onNameChanged={this.onNameChanged}
|
||||
/>
|
||||
}
|
||||
{isSetUserContactEmailDialogOpen &&
|
||||
<SysAdminUserSetContactEmailDialog
|
||||
toggle={this.toggleSetUserContactEmailDialog}
|
||||
onContactEmailChanged={this.onContactEmailChanged}
|
||||
/>
|
||||
}
|
||||
{isSetUserLoginIDDialogOpen &&
|
||||
<SysAdminUserSetLoginIDDialog
|
||||
toggle={this.toggleSetUserLoginIDDialog}
|
||||
onLoginIDChanged={this.onLoginIDChanged}
|
||||
/>
|
||||
}
|
||||
{isSetUserReferenceIDDialogOpen &&
|
||||
<SysAdminUserSetReferenceIDDialog
|
||||
toggle={this.toggleSetUserReferenceIDDialog}
|
||||
onReferenceIDChanged={this.onReferenceIDChanged}
|
||||
/>
|
||||
}
|
||||
{isSetUserQuotaDialogOpen &&
|
||||
<SysAdminUserSetQuotaDialog
|
||||
toggle={this.toggleSetUserQuotaDialog}
|
||||
onQuotaChanged={this.onQuotaChanged}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UserProfile.propTypes = propTypes;
|
||||
|
||||
export default UserProfile;
|
322
frontend/src/pages/sys-admin/users/user-repos.js
Normal file
322
frontend/src/pages/sys-admin/users/user-repos.js
Normal file
@@ -0,0 +1,322 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Link } from '@reach/router';
|
||||
import moment from 'moment';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { isPro, siteRoot, loginUrl, gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import Loading from '../../../components/loading';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
import TransferDialog from '../../../components/dialog/transfer-dialog';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import Nav from './user-nav';
|
||||
import OpMenu from './user-op-menu';
|
||||
|
||||
const { enableSysAdminViewRepo } = window.sysadmin.pageOptions;
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isItemFreezed: false
|
||||
};
|
||||
}
|
||||
|
||||
onFreezedItem = () => {
|
||||
this.setState({isItemFreezed: true});
|
||||
}
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({isItemFreezed: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No libraries')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%"></th>
|
||||
<th width="35%">{gettext('Name')}</th>
|
||||
<th width="30%">{gettext('Size')}</th>
|
||||
<th width="25%">{gettext('Last Update')}</th>
|
||||
<th width="5%">{/* Operations */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
onFreezedItem={this.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
deleteRepo={this.props.deleteRepo}
|
||||
transferRepo={this.props.transferRepo}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShown: false,
|
||||
highlight: false,
|
||||
isDeleteDialogOpen: false,
|
||||
isTransferDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseEnter = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShown: true,
|
||||
highlight: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseLeave = () => {
|
||||
if (!this.props.isItemFreezed) {
|
||||
this.setState({
|
||||
isOpIconShown: false,
|
||||
highlight: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onUnfreezedItem = () => {
|
||||
this.setState({
|
||||
highlight: false,
|
||||
isOpIconShow: false
|
||||
});
|
||||
this.props.onUnfreezedItem();
|
||||
}
|
||||
|
||||
toggleDeleteDialog = () => {
|
||||
this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen});
|
||||
}
|
||||
|
||||
deleteRepo = () => {
|
||||
this.props.deleteRepo(this.props.item.id);
|
||||
}
|
||||
|
||||
toggleTransferDialog = () => {
|
||||
this.setState({isTransferDialogOpen: !this.state.isTransferDialogOpen});
|
||||
}
|
||||
|
||||
transferRepo = (owner) => {
|
||||
this.props.transferRepo(this.props.item.id, owner.email);
|
||||
this.toggleTransferDialog();
|
||||
}
|
||||
|
||||
renderRepoName = () => {
|
||||
const { item } = this.props;
|
||||
const repo = item;
|
||||
if (repo.name) {
|
||||
if (isPro && enableSysAdminViewRepo && !repo.encrypted) {
|
||||
return <Link to={`${siteRoot}sys/libraries/${repo.id}/`}>{repo.name}</Link>;
|
||||
} else {
|
||||
return repo.name;
|
||||
}
|
||||
} else {
|
||||
return gettext('Broken ({repo_id_placeholder})')
|
||||
.replace('{repo_id_placeholder}', repo.id);
|
||||
}
|
||||
}
|
||||
|
||||
translateOperations = (item) => {
|
||||
let translateResult = '';
|
||||
switch (item) {
|
||||
case 'Delete':
|
||||
translateResult = gettext('Delete');
|
||||
break;
|
||||
case 'Transfer':
|
||||
translateResult = gettext('Transfer');
|
||||
break;
|
||||
}
|
||||
|
||||
return translateResult;
|
||||
}
|
||||
|
||||
onMenuItemClick = (operation) => {
|
||||
switch(operation) {
|
||||
case 'Delete':
|
||||
this.toggleDeleteDialog();
|
||||
break;
|
||||
case 'Transfer':
|
||||
this.toggleTransferDialog();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { item } = this.props;
|
||||
const { isOpIconShown, isDeleteDialogOpen, isTransferDialogOpen } = this.state;
|
||||
|
||||
const iconUrl = Utils.getLibIconUrl(item);
|
||||
const iconTitle = Utils.getLibIconTitle(item);
|
||||
|
||||
const itemName = '<span class="op-target">' + Utils.HTMLescape(item.name) + '</span>';
|
||||
const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||
<td>{this.renderRepoName()}</td>
|
||||
<td>{Utils.bytesToSize(item.size)}</td>
|
||||
<td>{moment(item.last_modify).fromNow()}</td>
|
||||
<td>
|
||||
{isOpIconShown &&
|
||||
<OpMenu
|
||||
operations={['Delete', 'Transfer']}
|
||||
translateOperations={this.translateOperations}
|
||||
onMenuItemClick={this.onMenuItemClick}
|
||||
onFreezedItem={this.props.onFreezedItem}
|
||||
onUnfreezedItem={this.onUnfreezedItem}
|
||||
/>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{isDeleteDialogOpen &&
|
||||
<CommonOperationConfirmationDialog
|
||||
title={gettext('Delete Library')}
|
||||
message={deleteDialogMsg}
|
||||
executeOperation={this.deleteRepo}
|
||||
confirmBtnText={gettext('Delete')}
|
||||
toggleDialog={this.toggleDeleteDialog}
|
||||
/>
|
||||
}
|
||||
{isTransferDialogOpen &&
|
||||
<TransferDialog
|
||||
itemName={item.name}
|
||||
submit={this.transferRepo}
|
||||
canTransferToDept={false}
|
||||
toggleDialog={this.toggleTransferDialog}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Repos extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
userInfo: {},
|
||||
repoList: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
seafileAPI.sysAdminGetUser(this.props.email).then((res) => {
|
||||
this.setState({
|
||||
userInfo: res.data
|
||||
});
|
||||
});
|
||||
seafileAPI.sysAdminListReposByOwner(this.props.email).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
repoList: res.data.repos
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteRepo = (repoID) => {
|
||||
seafileAPI.sysAdminDeleteRepo(repoID).then(res => {
|
||||
let newRepoList = this.state.repoList.filter(item => {
|
||||
return item.id != repoID;
|
||||
});
|
||||
this.setState({repoList: newRepoList});
|
||||
toaster.success(gettext('Successfully deleted 1 item.'));
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
transferRepo = (repoID, email) => {
|
||||
seafileAPI.sysAdminTransferRepo(repoID, email).then((res) => {
|
||||
let newRepoList = this.state.repoList.filter(item => {
|
||||
return item.id != repoID;
|
||||
});
|
||||
this.setState({repoList: newRepoList});
|
||||
let message = gettext('Successfully transferred the library.');
|
||||
toaster.success(message);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar />
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<Nav currentItem="owned-repos" email={this.props.email} userName={this.state.userInfo.name} />
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.repoList}
|
||||
deleteRepo={this.deleteRepo}
|
||||
transferRepo={this.transferRepo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Repos;
|
@@ -1,154 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import moment from 'moment';
|
||||
import Loading from '../../../components/loading';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('This user has no shared libraries')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">{/*icon*/}</th>
|
||||
<th width="20%">{gettext('Name')}</th>
|
||||
<th width="10%">{gettext('Share From')}</th>
|
||||
<th width="25%">{gettext('Size')}</th>
|
||||
<th width="19%">{gettext('Last Update')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
deleteRepo={this.props.deleteRepo}
|
||||
transferRepo={this.props.transferRepo}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
role: this.props.item.role,
|
||||
quota_total: this.props.item.quota_total
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({showOpIcon: true});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({showOpIcon: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
let { item } = this.props;
|
||||
|
||||
let iconUrl = Utils.getLibIconUrl(item);
|
||||
let iconTitle = Utils.getLibIconTitle(item);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||
<td>{item.name}</td>
|
||||
<td>{item.owner}</td>
|
||||
<td>{Utils.bytesToSize(item.size)}</td>
|
||||
<td>{moment(item.last_modify).fromNow()}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserShareInRepos extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
repoList: [],
|
||||
isShowImportWaitingDialog: false,
|
||||
isShowAddUserWaitingDialog: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
seafileAPI.sysAdminListShareInRepo(this.props.email).then(res => {
|
||||
this.setState({
|
||||
repoList: res.data.repo_list,
|
||||
loading: false
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.repoList}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserShareInRepos;
|
@@ -1,255 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import Loading from '../../../components/loading';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, shareLinkItems, uploadLinkItems } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('This user has not created any shared links')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">{/*icon*/}</th>
|
||||
<th width="20%">{gettext('Name')}</th>
|
||||
<th width="25%">{gettext('Size')}</th>
|
||||
<th width="10%">{gettext('Type')}</th>
|
||||
<th width="10%">{gettext('Visits')}</th>
|
||||
<th width="10%">{/*Operations*/}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{shareLinkItems.length > 0 && shareLinkItems.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
type={'Download'}
|
||||
deleteLink={this.props.deleteLink}
|
||||
/>);
|
||||
})}
|
||||
{uploadLinkItems.length > 0 && uploadLinkItems.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
type={'Upload'}
|
||||
deleteLink={this.props.deleteLink}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return shareLinkItems.length || uploadLinkItems.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
isDeleteDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({showOpIcon: true});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({showOpIcon: false});
|
||||
}
|
||||
|
||||
toggleDeleteDialog = () => {
|
||||
this.setState({isDeleteDialogOpen: !this.state.isDeleteDialogOpen});
|
||||
}
|
||||
|
||||
deleteLink = () => {
|
||||
this.props.deleteLink(this.props.item.token, this.props.type);
|
||||
this.toggleDeleteDialog();
|
||||
}
|
||||
|
||||
render() {
|
||||
let { showOpIcon, isDeleteDialogOpen } = this.state;
|
||||
let { item, type } = this.props;
|
||||
|
||||
let iconUrl;
|
||||
if (type == 'Upload' || (type== 'Download' && item.is_dir)) {
|
||||
iconUrl = Utils.getFolderIconUrl();
|
||||
} else {
|
||||
iconUrl = Utils.getFileIconUrl(item.obj_name);
|
||||
}
|
||||
|
||||
let itemName = '<span class="op-target">' + Utils.HTMLescape(item.obj_name == '/' ? item.repo_name : item.obj_name) + '</span>';
|
||||
let deleteDialogMsg = gettext('Are you sure you want to delete link {placeholder} ?'.replace('{placeholder}', itemName))
|
||||
|
||||
let iconVisibility = showOpIcon ? '' : ' invisible';
|
||||
let deleteIconClassName = 'op-icon sf2-icon-delete' + iconVisibility;
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td><img src={iconUrl} alt="" width="24" /></td>
|
||||
<td>{item.obj_name == '/' ? item.repo_name : item.obj_name}</td>
|
||||
<td>{Utils.bytesToSize(item.size)}</td>
|
||||
<td>{type}</td>
|
||||
<td>{item.view_cnt}</td>
|
||||
<td>
|
||||
<a href="#" className={deleteIconClassName} title={gettext('Delete')} onClick={this.toggleDeleteDialog}></a>
|
||||
</td>
|
||||
</tr>
|
||||
{isDeleteDialogOpen &&
|
||||
<CommonOperationConfirmationDialog
|
||||
title={gettext('Delete Link')}
|
||||
message={deleteDialogMsg}
|
||||
executeOperation={this.deleteLink}
|
||||
confirmBtnText={gettext('Delete')}
|
||||
toggleDialog={this.toggleDeleteDialog}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserShareLinks extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
repoList: {},
|
||||
shareLinkList: {},
|
||||
uploadLinkList: {},
|
||||
isShowImportWaitingDialog: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.getShareLinkList();
|
||||
this.getUploadLinkList();
|
||||
}
|
||||
|
||||
getShareLinkList = () => {
|
||||
seafileAPI.sysAdminListShareLinksByUser(this.props.email).then(res => {
|
||||
this.setState({
|
||||
shareLinkList: res.data.share_link_list,
|
||||
loading: false
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getUploadLinkList = () => {
|
||||
seafileAPI.sysAdminListUploadLinksByUser(this.props.email).then(res => {
|
||||
this.setState({
|
||||
uploadLinkList: res.data.upload_link_list,
|
||||
loading: false
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteLink = (token, type) => {
|
||||
if (type == 'Download') {
|
||||
seafileAPI.sysAdminRemoveShareLink(token).then(res => {
|
||||
let newShareLinkList = this.state.shareLinkList.filter(item=> {
|
||||
return item.token != token;
|
||||
});
|
||||
this.setState({
|
||||
shareLinkList: newShareLinkList
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
} else if (type == 'Upload') {
|
||||
seafileAPI.sysAdminRemoveShareLink(token).then(res => {
|
||||
let newUploadLinkList = this.state.uploadLinkList.filter(item=> {
|
||||
return item.token != token;
|
||||
});
|
||||
this.setState({
|
||||
uploadLinkList: newUploadLinkList
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
shareLinkItems={this.state.shareLinkList}
|
||||
uploadLinkItems={this.state.uploadLinkList}
|
||||
deleteLink={this.deleteLink}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserShareLinks;
|
183
frontend/src/pages/sys-admin/users/user-shared-repos.js
Normal file
183
frontend/src/pages/sys-admin/users/user-shared-repos.js
Normal file
@@ -0,0 +1,183 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Link } from '@reach/router';
|
||||
import moment from 'moment';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { isPro, siteRoot, loginUrl, gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import Loading from '../../../components/loading';
|
||||
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
|
||||
import TransferDialog from '../../../components/dialog/transfer-dialog';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
import Nav from './user-nav';
|
||||
import OpMenu from './user-op-menu';
|
||||
|
||||
const { enableSysAdminViewRepo } = window.sysadmin.pageOptions;
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No libraries')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%"></th>
|
||||
<th width="35%">{gettext('Name')}</th>
|
||||
<th width="20%">{gettext('Share From')}</th>
|
||||
<th width="20%">{gettext('Size')}</th>
|
||||
<th width="20%">{gettext('Last Update')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item
|
||||
key={index}
|
||||
item={item}
|
||||
/>);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
renderRepoName = () => {
|
||||
const { item } = this.props;
|
||||
const repo = item;
|
||||
if (repo.name) {
|
||||
if (isPro && enableSysAdminViewRepo && !repo.encrypted) {
|
||||
return <Link to={`${siteRoot}sys/libraries/${repo.id}/`}>{repo.name}</Link>;
|
||||
} else {
|
||||
return repo.name;
|
||||
}
|
||||
} else {
|
||||
return gettext('Broken ({repo_id_placeholder})')
|
||||
.replace('{repo_id_placeholder}', repo.id);
|
||||
}
|
||||
}
|
||||
|
||||
getOwnerUrl = () => {
|
||||
let url;
|
||||
const { item } = this.props;
|
||||
const index = item.owner_email.indexOf('@seafile_group');
|
||||
if (index == -1) {
|
||||
url = `${siteRoot}sys/users/${encodeURIComponent(item.owner_email)}/`;
|
||||
} else {
|
||||
const groupID = item.owner_email.substring(0, index);
|
||||
url = `${siteRoot}sys/departments/${groupID}/`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { item } = this.props;
|
||||
const iconUrl = Utils.getLibIconUrl(item);
|
||||
const iconTitle = Utils.getLibIconTitle(item);
|
||||
return (
|
||||
<Fragment>
|
||||
<tr>
|
||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||
<td>{this.renderRepoName()}</td>
|
||||
<td><Link to={this.getOwnerUrl()}>{item.owner_name}</Link></td>
|
||||
<td>{Utils.bytesToSize(item.size)}</td>
|
||||
<td>{moment(item.last_modify).fromNow()}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Repos extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
userInfo: {},
|
||||
repoList: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
seafileAPI.sysAdminGetUser(this.props.email).then((res) => {
|
||||
this.setState({
|
||||
userInfo: res.data
|
||||
});
|
||||
});
|
||||
seafileAPI.sysAdminListShareInRepos(this.props.email).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
repoList: res.data.repo_list
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar />
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<Nav currentItem="shared-repos" email={this.props.email} userName={this.state.userInfo.name} />
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.repoList}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Repos;
|
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import { siteRoot, gettext, haveLDAP } from '../../../utils/constants';
|
||||
import { siteRoot, gettext, haveLDAP, isDefaultAdmin } from '../../../utils/constants';
|
||||
|
||||
const propTypes = {
|
||||
currentItem: PropTypes.string.isRequired
|
||||
@@ -12,19 +12,19 @@ class Nav extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.navItems = [
|
||||
{name: 'database', urlPart: 'users', text: gettext('Database')},
|
||||
{name: 'admin', urlPart: 'users/admins', text: gettext('Admin')}
|
||||
{name: 'database', urlPart: 'users', text: gettext('Database')}
|
||||
];
|
||||
/*
|
||||
if (haveLDAP) {
|
||||
this.navItems.splice(1, 0,
|
||||
this.navItems.push(
|
||||
{name: 'ldap', urlPart: 'users/ldap', text: gettext('LDAP')},
|
||||
{name: 'ldap-imported', urlPart: 'users/ldap-imported', text: gettext('LDAP(imported)')}
|
||||
);
|
||||
this.navItems.splice(1, 0,
|
||||
{name: 'ldap', urlPart: 'users/ldap', text: gettext('LDAP')}
|
||||
}
|
||||
if (isDefaultAdmin) {
|
||||
this.navItems.push(
|
||||
{name: 'admin', urlPart: 'users/admins', text: gettext('Admin')}
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@@ -4,7 +4,7 @@ import { Button } from 'reactstrap';
|
||||
import moment from 'moment';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { isPro, username, gettext, multiInstitution, siteRoot } from '../../../utils/constants';
|
||||
import { isPro, username, gettext, multiInstitution, siteRoot, loginUrl } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import Loading from '../../../components/loading';
|
||||
@@ -386,14 +386,14 @@ class Item extends Component {
|
||||
</td>
|
||||
{(multiInstitution && !isAdmin) &&
|
||||
<td>
|
||||
<SelectEditor
|
||||
isTextMode={true}
|
||||
isEditIconShow={isOpIconShown && institutions.length > 0}
|
||||
options={institutions}
|
||||
currentOption={item.institution}
|
||||
onOptionChanged={this.updateInstitution}
|
||||
translateOption={this.translateInstitution}
|
||||
/>
|
||||
<SelectEditor
|
||||
isTextMode={true}
|
||||
isEditIconShow={isOpIconShown && institutions.length > 0}
|
||||
options={institutions}
|
||||
currentOption={item.institution}
|
||||
onOptionChanged={this.updateInstitution}
|
||||
translateOption={this.translateInstitution}
|
||||
/>
|
||||
</td>
|
||||
}
|
||||
<td>
|
||||
@@ -574,6 +574,7 @@ class UsersAll extends Component {
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
@@ -597,7 +598,7 @@ class UsersAll extends Component {
|
||||
userList: users,
|
||||
loading: false,
|
||||
hasNextPage: Utils.hasNextPage(page, perPage, res.data.total_count),
|
||||
currentPage: page,
|
||||
currentPage: page
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
@@ -659,17 +660,28 @@ class UsersAll extends Component {
|
||||
return user.email;
|
||||
});
|
||||
seafileAPI.sysAdminDeleteUserInBatch(emails).then(res => {
|
||||
let oldUserList = this.state.userList;
|
||||
let newUserList = oldUserList.filter(oldUser => {
|
||||
return !res.data.success.some(deletedUser =>{
|
||||
return deletedUser.email == oldUser.email;
|
||||
if (res.data.success.length) {
|
||||
let oldUserList = this.state.userList;
|
||||
let newUserList = oldUserList.filter(oldUser => {
|
||||
return !res.data.success.some(deletedUser =>{
|
||||
return deletedUser.email == oldUser.email;
|
||||
});
|
||||
});
|
||||
this.setState({
|
||||
userList: newUserList,
|
||||
hasUserSelected: emails.length != res.data.success.length
|
||||
});
|
||||
const length = res.data.success.length;
|
||||
const msg = length == 1 ?
|
||||
gettext('Successfully deleted 1 user.') :
|
||||
gettext('Successfully deleted {user_number_placeholder} users.')
|
||||
.replace('{user_number_placeholder}', length);
|
||||
toaster.success(msg);
|
||||
}
|
||||
res.data.failed.map(item => {
|
||||
const msg = `${item.email}: ${item.error_msg}`;
|
||||
toaster.danger(msg);
|
||||
});
|
||||
this.setState({
|
||||
userList: newUserList,
|
||||
hasUserSelected: emails.length != res .data.success.length
|
||||
});
|
||||
// todo: msg
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
@@ -678,13 +690,22 @@ class UsersAll extends Component {
|
||||
|
||||
importUserInBatch = (file) => {
|
||||
toaster.notify(gettext('It may take some time, please wait.'));
|
||||
// TODO: the url needs to be changed
|
||||
seafileAPI.sysAdminImportUserViaFile(file).then((res) => {
|
||||
// currently using old view in python, no return newUserList,
|
||||
// so after import new users, just send a get user list again.
|
||||
this.toggleImportUserDialog();
|
||||
this.getUsersListByPage(1);
|
||||
toaster.success(gettext('Import succeeded.'));
|
||||
if (res.data.success.length) {
|
||||
const users = res.data.success.map(item => {
|
||||
if (item.institution == undefined) {
|
||||
item.institution = '';
|
||||
}
|
||||
return new SysAdminUser(item);
|
||||
});
|
||||
this.setState({
|
||||
userList: users.concat(this.state.userList)
|
||||
});
|
||||
}
|
||||
res.data.failed.map(item => {
|
||||
const msg = `${item.email}: ${item.error_msg}`;
|
||||
toaster.danger(msg);
|
||||
});
|
||||
}).catch((error) => {
|
||||
let errMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMsg);
|
||||
@@ -853,6 +874,7 @@ class UsersAll extends Component {
|
||||
hasNextPage={this.state.hasNextPage}
|
||||
curPerPage={this.state.perPage}
|
||||
resetPerPage={this.resetPerPage}
|
||||
getListByPage={this.getUsersListByPage}
|
||||
updateUser={this.updateUser}
|
||||
deleteUser={this.deleteUser}
|
||||
updateAdminRole={this.updateAdminRole}
|
||||
@@ -860,7 +882,6 @@ class UsersAll extends Component {
|
||||
onUserSelected={this.onUserSelected}
|
||||
isAllUsersSelected={this.isAllUsersSelected}
|
||||
toggleSelectAllUsers={this.toggleSelectAllUsers}
|
||||
getListByPage={this.getUsersListByPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -125,7 +125,4 @@ export const canViewUserLog = window.sysadmin ? window.sysadmin.pageOptions.admi
|
||||
export const canViewAdminLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_admin_log : '';
|
||||
export const enableWorkWeixin = window.sysadmin ? window.sysadmin.pageOptions.enable_work_weixin : '';
|
||||
export const enableSysAdminViewRepo = window.sysadmin ? window.sysadmin.pageOptions.enableSysAdminViewRepo : '';
|
||||
export const enableTwoFactorAuth = window.sysadmin ? window.sysadmin.pageOptions.enable_two_factor_auth : '';
|
||||
export const sendEmailOnResettingUserPasswd = window.sysadmin ? window.sysadmin.pageOptions.send_email_on_resetting_user_passwd : '';
|
||||
export const isEmailConfiguredInSysAdmin = window.sysadmin ? window.sysadmin.pageOptions.is_email_configured : '';
|
||||
export const haveLDAP = window.sysadmin ? window.sysadmin.pageOptions.haveLDAP : '';
|
||||
|
Reference in New Issue
Block a user