mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-12 13:24:52 +00:00
Org user page (#2941)
* init org user * optimized code style * freezed item * update select-ediotr style * optimized code style * add state
This commit is contained in:
@@ -113,6 +113,11 @@ module.exports = {
|
|||||||
require.resolve('./polyfills'),
|
require.resolve('./polyfills'),
|
||||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||||
paths.appSrc + "/view-file-xmind.js",
|
paths.appSrc + "/view-file-xmind.js",
|
||||||
|
],
|
||||||
|
orgAdmin: [
|
||||||
|
require.resolve('./polyfills'),
|
||||||
|
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||||
|
paths.appSrc + "/pages/org-admin",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -71,6 +71,7 @@ module.exports = {
|
|||||||
viewFileText: [require.resolve('./polyfills'), paths.appSrc + "/view-file-text.js"],
|
viewFileText: [require.resolve('./polyfills'), paths.appSrc + "/view-file-text.js"],
|
||||||
viewFileImage: [require.resolve('./polyfills'), paths.appSrc + "/view-file-image.js"],
|
viewFileImage: [require.resolve('./polyfills'), paths.appSrc + "/view-file-image.js"],
|
||||||
viewFileXmind: [require.resolve('./polyfills'), paths.appSrc + "/view-file-xmind.js"],
|
viewFileXmind: [require.resolve('./polyfills'), paths.appSrc + "/view-file-xmind.js"],
|
||||||
|
orgAdmin: [require.resolve('./polyfills'), paths.appSrc + "/pages/org-admin"],
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
|
20
frontend/package-lock.json
generated
20
frontend/package-lock.json
generated
@@ -640,7 +640,7 @@
|
|||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
"resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
|
||||||
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
|
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.3.0",
|
"follow-redirects": "^1.3.0",
|
||||||
@@ -5318,7 +5318,7 @@
|
|||||||
},
|
},
|
||||||
"git-up": {
|
"git-up": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "http://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz",
|
||||||
"integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=",
|
"integrity": "sha1-JkSAoAax2EJhrB/gmjpRacV+oZ0=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-ssh": "^1.0.0",
|
"is-ssh": "^1.0.0",
|
||||||
@@ -5327,7 +5327,7 @@
|
|||||||
},
|
},
|
||||||
"git-url-parse": {
|
"git-url-parse": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "http://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz",
|
||||||
"integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=",
|
"integrity": "sha1-/j15xnRq4FBIz6UIyB553du6OEM=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"git-up": "^1.0.0"
|
"git-up": "^1.0.0"
|
||||||
@@ -7927,7 +7927,7 @@
|
|||||||
},
|
},
|
||||||
"node-status-codes": {
|
"node-status-codes": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
|
||||||
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8="
|
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8="
|
||||||
},
|
},
|
||||||
"noop6": {
|
"noop6": {
|
||||||
@@ -8234,7 +8234,7 @@
|
|||||||
},
|
},
|
||||||
"package.json": {
|
"package.json": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "http://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz",
|
||||||
"integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=",
|
"integrity": "sha1-+IYFnSpJ7QduZIg2ldc7K0bSHW0=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"git-package-json": "^1.4.0",
|
"git-package-json": "^1.4.0",
|
||||||
@@ -8244,7 +8244,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"got": {
|
"got": {
|
||||||
"version": "5.7.1",
|
"version": "5.7.1",
|
||||||
"resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz",
|
||||||
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
|
"integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"create-error-class": "^3.0.1",
|
"create-error-class": "^3.0.1",
|
||||||
@@ -8266,7 +8266,7 @@
|
|||||||
},
|
},
|
||||||
"package-json": {
|
"package-json": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "http://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz",
|
||||||
"integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
|
"integrity": "sha1-DRW9Z9HLvduyyiIv8u24a8sxqLs=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"got": "^5.0.0",
|
"got": "^5.0.0",
|
||||||
@@ -10932,9 +10932,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"seafile-js": {
|
"seafile-js": {
|
||||||
"version": "0.2.64",
|
"version": "0.2.66",
|
||||||
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.64.tgz",
|
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.66.tgz",
|
||||||
"integrity": "sha512-gaurvv8Gwq1IjXkHh1BufbeQxkmBRzxpNt/TqzOFWwBG2xsUW878T7HxiO+uw6amsc+K7PjYk2BVVmo29MgHqg==",
|
"integrity": "sha512-a51numCHkkMzNSp/7HpC0o/WYRF2m3+1g4yRPqASEnVXRSiZHiHY1fSR0W5eLmDqAmMoYiWdk99Y+kdjfhxb4A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"form-data": "^2.3.2",
|
"form-data": "^2.3.2",
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
"react-responsive": "^6.1.1",
|
"react-responsive": "^6.1.1",
|
||||||
"react-select": "^2.4.1",
|
"react-select": "^2.4.1",
|
||||||
"reactstrap": "^6.4.0",
|
"reactstrap": "^6.4.0",
|
||||||
"seafile-js": "^0.2.64",
|
"seafile-js": "^0.2.66",
|
||||||
"socket.io-client": "^2.2.0",
|
"socket.io-client": "^2.2.0",
|
||||||
"sw-precache-webpack-plugin": "0.11.4",
|
"sw-precache-webpack-plugin": "0.11.4",
|
||||||
"unified": "^7.0.0",
|
"unified": "^7.0.0",
|
||||||
|
91
frontend/src/components/dialog/org-add-admin-dialog.js
Normal file
91
frontend/src/components/dialog/org-add-admin-dialog.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import AsyncSelect from 'react-select/lib/Async';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
addOrgAdmin: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddOrgAdminDialog extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
selectedOption: null
|
||||||
|
};
|
||||||
|
this.options = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOptions = (value, callback) => {
|
||||||
|
if (value.trim().length > 0) {
|
||||||
|
seafileAPI.searchUsers(value.trim()).then((res) => {
|
||||||
|
this.options = [];
|
||||||
|
for (let i = 0 ; i < res.data.users.length; i++) {
|
||||||
|
let obj = {};
|
||||||
|
obj.value = res.data.users[i].name;
|
||||||
|
obj.email = res.data.users[i].email;
|
||||||
|
obj.label =
|
||||||
|
<Fragment>
|
||||||
|
<img src={res.data.users[i].avatar_url} className="select-module select-module-icon avatar" alt="Avatar"/>
|
||||||
|
<span className='select-module select-module-name'>{res.data.users[i].name}</span>
|
||||||
|
</Fragment>;
|
||||||
|
this.options.push(obj);
|
||||||
|
}
|
||||||
|
callback(this.options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectChange = (option) => {
|
||||||
|
this.setState({selectedOption: option});
|
||||||
|
this.options = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrgAdmin = () => {
|
||||||
|
let users = [];
|
||||||
|
if (this.state.selectedOption && this.state.selectedOption.length > 0 ) {
|
||||||
|
for (let i = 0; i < this.state.selectedOption.length; i ++) {
|
||||||
|
users[i] = this.state.selectedOption[i].email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.props.addOrgAdmin(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true}>
|
||||||
|
<ModalHeader>{gettext('Add Admins')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<AsyncSelect
|
||||||
|
inputId={'react-select-1-input'}
|
||||||
|
className='reviewer-select'
|
||||||
|
placeholder={gettext('Select a user as admin...')}
|
||||||
|
loadOptions={this.loadOptions}
|
||||||
|
onChange={this.handleSelectChange}
|
||||||
|
value={this.state.selectedOption}
|
||||||
|
maxMenuHeight={200}
|
||||||
|
isMulti
|
||||||
|
isFocused
|
||||||
|
isClearable
|
||||||
|
classNamePrefix
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={this.addOrgAdmin}>{gettext('Submit')}</Button>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Close')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddOrgAdminDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default AddOrgAdminDialog;
|
146
frontend/src/components/dialog/org-add-user-dialog.js
Normal file
146
frontend/src/components/dialog/org-add-user-dialog.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, Input, ModalHeader, ModalBody, Label, Form, InputGroup, InputGroupAddon, FormGroup } from 'reactstrap';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggle: PropTypes.func.isRequired,
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddOrgUserDialog extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isPasswordVisible: true,
|
||||||
|
email: '',
|
||||||
|
name: '',
|
||||||
|
password: '',
|
||||||
|
passwdnew: '',
|
||||||
|
errMessage: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
let isValid = this.validateInputParams();
|
||||||
|
if (isValid) {
|
||||||
|
let { email, name, password } = this.state;
|
||||||
|
this.props.handleSubmit(email, name, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.key == 'Enter') {
|
||||||
|
this.handleSubmit(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
togglePasswordVisible = () => {
|
||||||
|
this.setState({
|
||||||
|
isPasswordVisible: !this.state.isPasswordVisible
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generatePassword = () => {
|
||||||
|
let val = Math.random().toString(36).substr(5);
|
||||||
|
this.setState({
|
||||||
|
password: val,
|
||||||
|
passwdnew: val
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inputEmail = (e) => {
|
||||||
|
let email = e.target.value.trim();
|
||||||
|
this.setState({email: email});
|
||||||
|
}
|
||||||
|
|
||||||
|
inputName = (e) => {
|
||||||
|
let name = e.target.value.trim();
|
||||||
|
this.setState({name: name});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inputPassword = (e) => {
|
||||||
|
let passwd = e.target.value.trim();
|
||||||
|
this.setState({password: passwd});
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPasswordNew = (e) => {
|
||||||
|
let passwd = e.target.value.trim();
|
||||||
|
this.setState({passwdnew: passwd});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggle();
|
||||||
|
};
|
||||||
|
|
||||||
|
validateInputParams() {
|
||||||
|
let errMessage = '';
|
||||||
|
let email = this.state.email;
|
||||||
|
if (!email.length) {
|
||||||
|
errMessage = 'email is required';
|
||||||
|
this.setState({errMessage: errMessage});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let password1 = this.state.password;
|
||||||
|
let password2 = this.state.passwdnew;
|
||||||
|
if (!password1.length) {
|
||||||
|
errMessage = 'Please enter password';
|
||||||
|
this.setState({errMessage: errMessage});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!password2.length) {
|
||||||
|
errMessage = 'Please enter the password again';
|
||||||
|
this.setState({errMessage: errMessage});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (password1 !== password2) {
|
||||||
|
errMessage = 'Passwords don\'t match';
|
||||||
|
this.setState({errMessage: errMessage});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Add User')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Email')}</Label>
|
||||||
|
<Input value={this.state.email || ''} onChange={this.inputEmail} />
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Name(optional)')}</Label>
|
||||||
|
<Input value={this.state.name || ''} onChange={this.inputName} />
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Password')}</Label>
|
||||||
|
<InputGroup className="passwd">
|
||||||
|
<Input type={this.state.isPasswordVisible ? 'text' : 'password'} value={this.state.password || ''} onChange={this.inputPassword}/>
|
||||||
|
<InputGroupAddon addonType="append">
|
||||||
|
<Button onClick={this.togglePasswordVisible}><i className={`link-operation-icon fas ${this.state.isPasswordVisible ? 'fa-eye': 'fa-eye-slash'}`}></i></Button>
|
||||||
|
<Button onClick={this.generatePassword}><i className="link-operation-icon fas fa-magic"></i></Button>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup>
|
||||||
|
<Label>{gettext('Confirm Password')}</Label>
|
||||||
|
<Input className="passwd" type={this.state.isPasswordVisible ? 'text' : 'password'} value={this.state.passwdnew || ''} onChange={this.inputPasswordNew} />
|
||||||
|
</FormGroup>
|
||||||
|
<Button onClick={this.handleSubmit}>{gettext('Submit')}</Button>
|
||||||
|
</Form>
|
||||||
|
<Label className="err-message">{gettext(this.state.errMessage)}</Label>
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddOrgUserDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default AddOrgUserDialog;
|
43
frontend/src/components/select-editor/user-status-editor.js
Normal file
43
frontend/src/components/select-editor/user-status-editor.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import SelectEditor from './select-editor';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
isTextMode: PropTypes.bool.isRequired,
|
||||||
|
isEditIconShow: PropTypes.bool.isRequired,
|
||||||
|
statusArray: PropTypes.array.isRequired,
|
||||||
|
currentStatus: PropTypes.string.isRequired,
|
||||||
|
onStatusChanged: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class UserStatusEditor extends React.Component {
|
||||||
|
|
||||||
|
translateStatus = (userStatus) => {
|
||||||
|
if (userStatus === 'active') {
|
||||||
|
return gettext('Active');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userStatus === 'inactive') {
|
||||||
|
return gettext('Inactive');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<SelectEditor
|
||||||
|
isTextMode={this.props.isTextMode}
|
||||||
|
isEditIconShow={this.props.isEditIconShow}
|
||||||
|
options={this.props.statusArray}
|
||||||
|
currentOption={this.props.currentStatus}
|
||||||
|
onOptionChanged={this.props.onStatusChanged}
|
||||||
|
translateOption={this.translateStatus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UserStatusEditor.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default UserStatusEditor;
|
@@ -129,10 +129,6 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cur-view-content .permission-editor-select .permission-editor__control {
|
|
||||||
height: 24px;
|
|
||||||
min-height: 24px;
|
|
||||||
}
|
|
||||||
.cur-view-detail {
|
.cur-view-detail {
|
||||||
flex: 0 0 20rem;
|
flex: 0 0 20rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
9
frontend/src/css/org-admin-paginator.css
Normal file
9
frontend/src/css/org-admin-paginator.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.paginator {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-view-path.org-user-nav {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
@@ -13,4 +13,21 @@
|
|||||||
}
|
}
|
||||||
.permission-editor .permission-editor__control .permission-editor-explanation {
|
.permission-editor .permission-editor__control .permission-editor-explanation {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cur-view-content .permission-editor-select .permission-editor__control,
|
||||||
|
.cur-view-content .permission-editor-select .permission-editor__control div,
|
||||||
|
.cur-view-content .permission-editor-select .permission-editor__control .permission-editor__input,
|
||||||
|
.cur-view-content .permission-editor-select .permission-editor__indicators {
|
||||||
|
height: 1.5rem;
|
||||||
|
min-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-view-content .permission-editor-select .permission-editor__value-container div:nth-child(2) {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-view-content .permission-editor-select .permission-editor__indicators .permission-editor__indicator {
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
21
frontend/src/models/org-user.js
Normal file
21
frontend/src/models/org-user.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Utils } from '../utils/utils';
|
||||||
|
import { lang } from '../utils/constants';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
moment.locale(lang);
|
||||||
|
|
||||||
|
class OrgUserInfo {
|
||||||
|
constructor(object) {
|
||||||
|
this.id = object.id;
|
||||||
|
this.name = object.name;
|
||||||
|
this.email = object.email;
|
||||||
|
this.contact_email = object.owner_contact_email;
|
||||||
|
this.is_active = object.is_active;
|
||||||
|
this.quota = object.quota > 0 ? Utils.bytesToSize(object.quota) : '';
|
||||||
|
this.self_usage = Utils.bytesToSize(object.self_usage);
|
||||||
|
this.last_login = object.last_login ? moment(object.last_login).fromNow() : '--';
|
||||||
|
this.ctime = moment(object.ctime).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgUserInfo;
|
82
frontend/src/pages/org-admin/index.js
Normal file
82
frontend/src/pages/org-admin/index.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Import React!
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Router } from '@reach/router';
|
||||||
|
import { siteRoot } from '../../utils/constants';
|
||||||
|
import SidePanel from './side-panel';
|
||||||
|
import MainPanel from './main-panel';
|
||||||
|
import OrgUsers from './org-users';
|
||||||
|
import OrgUsersList from './org-users-list';
|
||||||
|
import OrgAdminList from './org-admin-list';
|
||||||
|
|
||||||
|
import '../../assets/css/fa-solid.css';
|
||||||
|
import '../../assets/css/fa-regular.css';
|
||||||
|
import '../../assets/css/fontawesome.css';
|
||||||
|
import '../../css/layout.css';
|
||||||
|
import '../../css/toolbar.css';
|
||||||
|
|
||||||
|
class Org extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isSidePanelClosed: false,
|
||||||
|
isShowAddOrgUserDialog: false,
|
||||||
|
isShowAddOrgAdminDialog: false,
|
||||||
|
currentTab: 'users',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
let href = window.location.href.split('/');
|
||||||
|
let currentTab = href[href.length - 2];
|
||||||
|
if (currentTab == 'useradmin') {
|
||||||
|
currentTab = 'users';
|
||||||
|
}
|
||||||
|
this.setState({currentTab: currentTab});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseSidePanel = () => {
|
||||||
|
this.setState({isSidePanelClosed: !this.state.isSidePanelClosed});
|
||||||
|
}
|
||||||
|
|
||||||
|
tabItemClick = (param) => {
|
||||||
|
this.setState({currentTab: param});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddOrgUser = () => {
|
||||||
|
this.setState({isShowAddOrgUserDialog: !this.state.isShowAddOrgUserDialog});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddOrgAdmin = () => {
|
||||||
|
this.setState({isShowAddOrgAdminDialog: !this.state.isShowAddOrgAdminDialog});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
let { isSidePanelClosed, currentTab, isShowAddOrgUserDialog, isShowAddOrgAdminDialog } = this.state;
|
||||||
|
return (
|
||||||
|
<div id="main">
|
||||||
|
<SidePanel isSidePanelClosed={isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} />
|
||||||
|
<MainPanel>
|
||||||
|
<Router>
|
||||||
|
<OrgUsers
|
||||||
|
path={siteRoot + "org/useradmin"}
|
||||||
|
currentTab={currentTab}
|
||||||
|
tabItemClick={this.tabItemClick}
|
||||||
|
toggleAddOrgAdmin={this.toggleAddOrgAdmin}
|
||||||
|
toggleAddOrgUser={this.toggleAddOrgUser}
|
||||||
|
>
|
||||||
|
<OrgUsersList path="/" currentTab={currentTab} isShowAddOrgUserDialog={isShowAddOrgUserDialog} toggleAddOrgUser={this.toggleAddOrgUser} />
|
||||||
|
<OrgAdminList path="admins" currentTab={currentTab} isShowAddOrgAdminDialog={isShowAddOrgAdminDialog} toggleAddOrgAdmin={this.toggleAddOrgAdmin} />
|
||||||
|
</OrgUsers>
|
||||||
|
</Router>
|
||||||
|
</MainPanel>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Org />,
|
||||||
|
document.getElementById('wrapper')
|
||||||
|
);
|
30
frontend/src/pages/org-admin/main-panel.js
Normal file
30
frontend/src/pages/org-admin/main-panel.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Account from '../../components/common/account';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
children: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainPanel extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="main-panel o-hidden">
|
||||||
|
<div className="main-panel-north border-left-show">
|
||||||
|
<div className="cur-view-toolbar">
|
||||||
|
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||||
|
</div>
|
||||||
|
<div className="common-toolbar">
|
||||||
|
<Account />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MainPanel.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default MainPanel;
|
125
frontend/src/pages/org-admin/org-admin-list.js
Normal file
125
frontend/src/pages/org-admin/org-admin-list.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext, orgID } from '../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import Toast from '../../components/toast';
|
||||||
|
import OrgUserInfo from '../../models/org-user';
|
||||||
|
import ModalPortal from '../../components/modal-portal';
|
||||||
|
import AddOrgAdminDialog from '../../components/dialog/org-add-admin-dialog';
|
||||||
|
import UserItem from './org-user-item';
|
||||||
|
|
||||||
|
import '../../css/org-admin-paginator.css';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggleAddOrgAdmin: PropTypes.func.isRequired,
|
||||||
|
currentTab: PropTypes.string.isRequired,
|
||||||
|
isShowAddOrgAdminDialog: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class OrgAdminList extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
orgAdminUsers: [],
|
||||||
|
isItemFreezed: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
seafileAPI.listOrgUsers(orgID, true).then(res => {
|
||||||
|
let userList = res.data.user_list.map(item => {
|
||||||
|
return new OrgUserInfo(item);
|
||||||
|
});
|
||||||
|
this.setState({orgAdminUsers: userList});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFreezedItem = () => {
|
||||||
|
this.setState({isItemFreezed: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnfreezedItem = () => {
|
||||||
|
this.setState({isItemFreezed: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDelete = (email) => {
|
||||||
|
seafileAPI.deleteOrgUser(orgID, email).then(res => {
|
||||||
|
this.setState({
|
||||||
|
orgAdminUsers: this.state.orgAdminUsers.filter(item => item.email != email)
|
||||||
|
});
|
||||||
|
let msg = gettext('Successfully deleted %s');
|
||||||
|
msg = msg.replace('%s', email);
|
||||||
|
Toast.success(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleRevokeAdmin = (email) => {
|
||||||
|
seafileAPI.setOrgAdmin(orgID, email, false).then(res => {
|
||||||
|
this.setState({
|
||||||
|
orgAdminUsers: this.state.orgAdminUsers.filter(item => item.email != email)
|
||||||
|
});
|
||||||
|
let msg = gettext('Successfully revoke the admin permission of %s');
|
||||||
|
msg = msg.replace('%s', email);
|
||||||
|
Toast.success(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrgAdmin = (users) => {
|
||||||
|
seafileAPI.setOrgAdmin(orgID, users, true).then(res => {
|
||||||
|
let userInfo = new OrgUserInfo(res.data);
|
||||||
|
this.state.orgAdminUsers.unshift(userInfo);
|
||||||
|
this.setState({
|
||||||
|
orgAdminUsers: this.state.orgAdminUsers
|
||||||
|
});
|
||||||
|
this.props.toggleAddOrgAdmin();
|
||||||
|
let msg = gettext('Successfully set %s as admin.');
|
||||||
|
msg = msg.replace('%s', userInfo.email);
|
||||||
|
Toast.success(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let orgAdminUsers = this.state.orgAdminUsers;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="cur-view-content">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="30%">{gettext('Name')}</th>
|
||||||
|
<th width="15%">{gettext('Status')}</th>
|
||||||
|
<th width="15%">{gettext('Space Used')}</th>
|
||||||
|
<th width="20%">{gettext('Create At / Last Login')}</th>
|
||||||
|
<th width="20%" className="text-center">{gettext('Operations')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{orgAdminUsers.map(item => {
|
||||||
|
return (
|
||||||
|
<UserItem
|
||||||
|
key={item.id}
|
||||||
|
user={item}
|
||||||
|
currentTab={this.props.currentTab}
|
||||||
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
|
toggleDelete={this.toggleDelete}
|
||||||
|
toggleRevokeAdmin={this.toggleRevokeAdmin}
|
||||||
|
onFreezedItem={this.onFreezedItem}
|
||||||
|
onUnfreezedItem={this.onUnfreezedItem}
|
||||||
|
/>
|
||||||
|
)})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{this.props.isShowAddOrgAdminDialog && (
|
||||||
|
<ModalPortal>
|
||||||
|
<AddOrgAdminDialog toggle={this.props.toggleAddOrgAdmin} addOrgAdmin={this.addOrgAdmin} />
|
||||||
|
</ModalPortal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OrgAdminList.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default OrgAdminList;
|
160
frontend/src/pages/org-admin/org-user-item.js
Normal file
160
frontend/src/pages/org-admin/org-user-item.js
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||||
|
import { gettext, siteRoot, orgID, username } from '../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import Toast from '../../components/toast';
|
||||||
|
import UserStatusEditor from '../../components/select-editor/user-status-editor';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
currentTab: PropTypes.string,
|
||||||
|
toggleRevokeAdmin: PropTypes.func,
|
||||||
|
isItemFreezed: PropTypes.bool.isRequired,
|
||||||
|
toggleDelete: PropTypes.func.isRequired,
|
||||||
|
onFreezedItem: PropTypes.func.isRequired,
|
||||||
|
onUnfreezedItem: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class UserItem extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
highlight: false,
|
||||||
|
showMenu: false,
|
||||||
|
currentStatus: this.props.user.is_active ? 'active' : 'inactive',
|
||||||
|
isItemMenuShow: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.statusArray = ['active', 'inactive'];
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = () => {
|
||||||
|
if (!this.props.isItemFreezed) {
|
||||||
|
this.setState({
|
||||||
|
showMenu: true,
|
||||||
|
highlight: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
if (!this.props.isItemFreezed) {
|
||||||
|
this.setState({
|
||||||
|
showMenu: false,
|
||||||
|
highlight: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDelete = () => {
|
||||||
|
const email = this.props.user.email;
|
||||||
|
this.props.toggleDelete(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleResetPW = () => {
|
||||||
|
const email = this.props.user.email;
|
||||||
|
seafileAPI.resetOrgUserPassword(orgID, email).then(res => {
|
||||||
|
let msg;
|
||||||
|
msg = gettext('Successfully reset password to %(passwd)s for user %(user)s.');
|
||||||
|
msg = msg.replace('%(passwd)s', res.data.new_password);
|
||||||
|
msg = msg.replace('%(user)s', email);
|
||||||
|
Toast.success(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleRevokeAdmin = () => {
|
||||||
|
const email = this.props.user.email;
|
||||||
|
this.props.toggleRevokeAdmin(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeStatus = (st) => {
|
||||||
|
let statusCode;
|
||||||
|
if (st == 'active') {
|
||||||
|
statusCode = 1;
|
||||||
|
} else {
|
||||||
|
statusCode = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
seafileAPI.changeOrgUserStatus(this.props.user.id, statusCode).then(res => {
|
||||||
|
this.setState({
|
||||||
|
currentStatus: statusCode == 1 ? 'active' : 'inactive',
|
||||||
|
highlight: false,
|
||||||
|
showMenu: false,
|
||||||
|
});
|
||||||
|
Toast.success(gettext('Edit succeeded.'));
|
||||||
|
}).catch(err => {
|
||||||
|
Toast.danger(gettext('Edit falied.'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropdownToggleClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.toggleOperationMenu(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOperationMenu = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.setState(
|
||||||
|
{isItemMenuShow: !this.state.isItemMenuShow }, () => {
|
||||||
|
if (this.state.isItemMenuShow) {
|
||||||
|
this.props.onFreezedItem();
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
highlight: false,
|
||||||
|
showMenu: false,
|
||||||
|
});
|
||||||
|
this.props.onUnfreezedItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { user, currentTab } = this.props;
|
||||||
|
let href = siteRoot + 'org/useradmin/info/' + encodeURIComponent(user.email) + '/';
|
||||||
|
let isOperationMenuShow = (user.email !== username) && this.state.showMenu;
|
||||||
|
let isEditIconShow = isOperationMenuShow;
|
||||||
|
return (
|
||||||
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
|
<td>
|
||||||
|
<a href={href} className="font-weight-normal">{user.name}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<UserStatusEditor
|
||||||
|
isTextMode={true}
|
||||||
|
isEditIconShow={isEditIconShow}
|
||||||
|
currentStatus={this.state.currentStatus}
|
||||||
|
statusArray={this.statusArray}
|
||||||
|
onStatusChanged={this.changeStatus}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{user.quota ? user.self_usage + ' / ' + user.quota : user.self_usage}</td>
|
||||||
|
<td style={{'fontSize': '11px'}}>{user.ctime} / {user.last_login ? user.last_login : '--'}</td>
|
||||||
|
<td className="text-center cursor-pointer">
|
||||||
|
{isOperationMenuShow && (
|
||||||
|
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
||||||
|
<DropdownToggle
|
||||||
|
tag="a"
|
||||||
|
className="fas fa-ellipsis-v"
|
||||||
|
title={gettext('More Operations')}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded={this.state.isItemMenuShow}
|
||||||
|
onClick={this.onDropdownToggleClick}
|
||||||
|
/>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownItem onClick={this.toggleDelete}>{gettext('Delete')}</DropdownItem>
|
||||||
|
<DropdownItem onClick={this.toggleResetPW}>{gettext('ResetPwd')}</DropdownItem>
|
||||||
|
{currentTab == 'admins' && <DropdownItem onClick={this.toggleRevokeAdmin}>{gettext('Revoke Admin')}</DropdownItem>}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UserItem.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default UserItem;
|
142
frontend/src/pages/org-admin/org-users-list.js
Normal file
142
frontend/src/pages/org-admin/org-users-list.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext, orgID } from '../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import OrgUserInfo from '../../models/org-user';
|
||||||
|
import Toast from '../../components/toast';
|
||||||
|
import ModalPortal from '../../components/modal-portal';
|
||||||
|
import AddOrgUserDialog from '../../components/dialog/org-add-user-dialog';
|
||||||
|
import UserItem from './org-user-item';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggleAddOrgUser: PropTypes.func.isRequired,
|
||||||
|
currentTab: PropTypes.string.isRequired,
|
||||||
|
isShowAddOrgUserDialog: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class OrgUsersList extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
orgUsers: [],
|
||||||
|
isItemFreezed: false,
|
||||||
|
page: 1,
|
||||||
|
pageNext: 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
let page = this.state.page;
|
||||||
|
this.initData(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
initData = (page) => {
|
||||||
|
seafileAPI.listOrgUsers(orgID, '', page).then(res => {
|
||||||
|
let userList = res.data.user_list.map(item => {
|
||||||
|
return new OrgUserInfo(item);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
orgUsers: userList,
|
||||||
|
pageNext: res.data.page_next,
|
||||||
|
page: res.data.page,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFreezedItem = () => {
|
||||||
|
this.setState({isItemFreezed: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnfreezedItem = () => {
|
||||||
|
this.setState({isItemFreezed: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDelete = (email) => {
|
||||||
|
seafileAPI.deleteOrgUser(orgID, email).then(res => {
|
||||||
|
let users = this.state.orgUsers.filter(item => item.email != email);
|
||||||
|
this.setState({orgUsers: users});
|
||||||
|
let msg = gettext('Successfully deleted %s');
|
||||||
|
msg = msg.replace('%s', email);
|
||||||
|
Toast.success(msg);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = (email, name, password) => {
|
||||||
|
seafileAPI.addOrgUser(orgID, email, name, password).then(res => {
|
||||||
|
let userInfo = new OrgUserInfo(res.data);
|
||||||
|
this.state.orgUsers.unshift(userInfo);
|
||||||
|
this.setState({
|
||||||
|
orgUsers: this.state.orgUsers
|
||||||
|
});
|
||||||
|
this.props.toggleAddOrgUser();
|
||||||
|
let msg;
|
||||||
|
msg = gettext('successfully added user %s.');
|
||||||
|
msg = msg.replace('%s', email);
|
||||||
|
Toast.success(msg);
|
||||||
|
}).catch(err => {
|
||||||
|
Toast.danger(err.response.data.error_msg);
|
||||||
|
this.props.toggleAddOrgUser();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangePageNum = (e, num) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let page = this.state.page;
|
||||||
|
|
||||||
|
if (num == 1) {
|
||||||
|
page = page + 1;
|
||||||
|
} else {
|
||||||
|
page = page - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initData(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let orgUsers = this.state.orgUsers;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="cur-view-content">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="30%">{gettext('Name')}</th>
|
||||||
|
<th width="15%">{gettext('Status')}</th>
|
||||||
|
<th width="15%">{gettext('Space Used')}</th>
|
||||||
|
<th width="20%">{gettext('Create At / Last Login')}</th>
|
||||||
|
<th width="20%" className="text-center">{gettext('Operations')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{orgUsers.map(item => {
|
||||||
|
return (
|
||||||
|
<UserItem
|
||||||
|
key={item.id}
|
||||||
|
user={item}
|
||||||
|
currentTab={this.props.currentTab}
|
||||||
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
|
toggleDelete={this.toggleDelete}
|
||||||
|
onFreezedItem={this.onFreezedItem}
|
||||||
|
onUnfreezedItem={this.onUnfreezedItem}
|
||||||
|
/>
|
||||||
|
)})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="paginator">
|
||||||
|
{this.state.page !=1 && <a href="#" onClick={(e) => this.onChangePageNum(e, -1)}>{gettext("Previous")}{' | '}</a>}
|
||||||
|
{this.state.pageNext && <a href="#" onClick={(e) => this.onChangePageNum(e, 1)}>{gettext("Next")}</a>}
|
||||||
|
</div>
|
||||||
|
{this.props.isShowAddOrgUserDialog && (
|
||||||
|
<ModalPortal>
|
||||||
|
<AddOrgUserDialog toggle={this.props.toggleAddOrgUser} handleSubmit={this.handleSubmit} />
|
||||||
|
</ModalPortal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OrgUsersList.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default OrgUsersList;
|
58
frontend/src/pages/org-admin/org-users.js
Normal file
58
frontend/src/pages/org-admin/org-users.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from '@reach/router';
|
||||||
|
|
||||||
|
import { siteRoot, gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
class OrgUsers extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
tabItemClick = (param) => {
|
||||||
|
this.props.tabItemClick(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddOrgUser = () => {
|
||||||
|
this.props.toggleAddOrgUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddOrgAdmin = () => {
|
||||||
|
this.props.toggleAddOrgAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<div className="cur-view-path org-user-nav">
|
||||||
|
<ul className="nav">
|
||||||
|
<li className="nav-item" onClick={() => this.tabItemClick('users')}>
|
||||||
|
<Link className={`nav-link ${this.props.currentTab === 'users' ? 'active': ''}`} to={siteRoot + "org/useradmin/"} title={gettext('All')}>{gettext('All')}</Link>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item" onClick={() => this.tabItemClick('admins')}>
|
||||||
|
<Link className={`nav-link ${this.props.currentTab === 'admins' ? 'active': ''}`} to={siteRoot + "org/useradmin/admins/"} title={gettext('Admin')}>{gettext('Admin')}</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div className="operation mt-1">
|
||||||
|
{this.props.currentTab === 'users' &&
|
||||||
|
<button className="btn btn-secondary operation-item" title={gettext('Add user')} onClick={this.toggleAddOrgUser}>
|
||||||
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add user')}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
{this.props.currentTab === 'admins' &&
|
||||||
|
<button className="btn btn-secondary operation-item" title={gettext('Add admin')} onClick={this.toggleAddOrgAdmin}>
|
||||||
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add admin')}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgUsers;
|
78
frontend/src/pages/org-admin/side-panel.js
Normal file
78
frontend/src/pages/org-admin/side-panel.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from '@reach/router';
|
||||||
|
import Logo from '../../components/logo';
|
||||||
|
import { gettext, siteRoot } from '../../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
isSidePanelClosed: PropTypes.bool.isRequired,
|
||||||
|
onCloseSidePanel: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class SidePanel extends React.Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={`side-panel ${this.props.isSidePanelClosed ? '' : 'left-zero'}`}>
|
||||||
|
<div className="side-panel-north">
|
||||||
|
<Logo onCloseSidePanel={this.props.onCloseSidePanel}/>
|
||||||
|
</div>
|
||||||
|
<div className="side-panel-center">
|
||||||
|
<div className="side-nav">
|
||||||
|
<div className="side-nav-con">
|
||||||
|
<h3 className="sf-heading" style={{ 'color': '#f7941d' }}>{gettext('Admin')}</h3>
|
||||||
|
<ul className="nav nav-pills flex-column nav-container">
|
||||||
|
<li className="nav-item">
|
||||||
|
<a className='nav-link ellipsis' href={siteRoot + "org/orgmanage/"}>
|
||||||
|
<span className="sf2-icon-monitor" aria-hidden="true"></span>
|
||||||
|
<span className="nav-text">{gettext('Info')}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<a className='nav-link ellipsis' href={siteRoot + "org/repoadmin/"}>
|
||||||
|
<span className="sf2-icon-library"></span>
|
||||||
|
<span className="nav-text">{gettext('Libraries')}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<Link className='nav-link ellipsis active' to={siteRoot + "org/useradmin/"}>
|
||||||
|
<span className="sf2-icon-user"></span>
|
||||||
|
<span className="nav-text">{gettext('Users')}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<a href="/org/admin/#address-book/" className="nav-link ellipsis">
|
||||||
|
<span className="sf2-icon-organization"></span>
|
||||||
|
<span className="nav-text">{gettext('Departments')}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<a href="/org/groupadmin/" className="nav-link ellipsis">
|
||||||
|
<span className="sf2-icon-group"></span>
|
||||||
|
<span className="nav-text">{gettext('Groups')}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<a href="/org/publinkadmin/" className="nav-link ellipsis">
|
||||||
|
<span className="sf2-icon-link"></span>
|
||||||
|
<span className="nav-text">{gettext('Links')}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<a href="/org/file-audit-admin/" className="nav-link ellipsis">
|
||||||
|
<span className="sf2-icon-clock"></span>
|
||||||
|
<span className="nav-text">{gettext('Logs')}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SidePanel.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SidePanel;
|
@@ -75,3 +75,6 @@ export const author = window.draftReview ? window.draftReview.config.author : ''
|
|||||||
export const authorAvatar = window.draftReview ? window.draftReview.config.authorAvatar : '';
|
export const authorAvatar = window.draftReview ? window.draftReview.config.authorAvatar : '';
|
||||||
export const originFileExists = window.draftReview ? window.draftReview.config.originFileExists : '';
|
export const originFileExists = window.draftReview ? window.draftReview.config.originFileExists : '';
|
||||||
export const draftFileExists = window.draftReview ? window.draftReview.config.draftFileExists : '';
|
export const draftFileExists = window.draftReview ? window.draftReview.config.draftFileExists : '';
|
||||||
|
|
||||||
|
// org admin
|
||||||
|
export const orgID = window.org ? window.org.pageOptions.orgID : '';
|
||||||
|
@@ -93,6 +93,7 @@
|
|||||||
.sf2-icon-readme:before {content:"\e039"}
|
.sf2-icon-readme:before {content:"\e039"}
|
||||||
.sf2-icon-drafts:before {content:"\e03a"}
|
.sf2-icon-drafts:before {content:"\e03a"}
|
||||||
.sf2-icon-recycle:before {content:"\e03b"}
|
.sf2-icon-recycle:before {content:"\e03b"}
|
||||||
|
.sf2-icon-library:before { content:"\e00d"; }
|
||||||
|
|
||||||
/* common class and element style*/
|
/* common class and element style*/
|
||||||
a { color:#eb8205; }
|
a { color:#eb8205; }
|
||||||
|
@@ -94,3 +94,12 @@ class IsProVersion(BasePermission):
|
|||||||
|
|
||||||
def has_permission(self, request, *args, **kwargs):
|
def has_permission(self, request, *args, **kwargs):
|
||||||
return is_pro_version()
|
return is_pro_version()
|
||||||
|
|
||||||
|
class IsOrgAdminUser(BasePermission):
|
||||||
|
"""
|
||||||
|
Check whether user is org admin
|
||||||
|
"""
|
||||||
|
def has_permission(self, request, view, obj=None):
|
||||||
|
org_id = int(view.kwargs.get('org_id', ''))
|
||||||
|
return True if request.user.org.is_staff and \
|
||||||
|
request.user.org.org_id == org_id else False
|
||||||
|
Reference in New Issue
Block a user