mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-09 19:01:42 +00:00
[org admin] added sort for 'libraries/users' (#4548)
* users: split 'all' & 'admin' * bugfix & improvement for pages
This commit is contained in:
@@ -4,6 +4,8 @@ class OrgAdminRepo {
|
|||||||
this.repoName = object.repo_name;
|
this.repoName = object.repo_name;
|
||||||
this.ownerName = object.owner_name;
|
this.ownerName = object.owner_name;
|
||||||
this.ownerEmail = object.owner_email;
|
this.ownerEmail = object.owner_email;
|
||||||
|
this.size = object.size;
|
||||||
|
this.file_count = object.file_count;
|
||||||
this.encrypted = object.encrypted;
|
this.encrypted = object.encrypted;
|
||||||
this.isDepartmentRepo = object.is_department_repo;
|
this.isDepartmentRepo = object.is_department_repo;
|
||||||
this.groupID = object.group_id;
|
this.groupID = object.group_id;
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
// Import React!
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Router } from '@reach/router';
|
import { Router } from '@reach/router';
|
||||||
import { siteRoot } from '../../utils/constants';
|
import { siteRoot } from '../../utils/constants';
|
||||||
import SidePanel from './side-panel';
|
import SidePanel from './side-panel';
|
||||||
import OrgUsers from './org-users';
|
import OrgUsers from './org-users-users';
|
||||||
|
import OrgAdmins from './org-users-admins';
|
||||||
import OrgUserProfile from './org-user-profile';
|
import OrgUserProfile from './org-user-profile';
|
||||||
import OrgUserRepos from './org-user-repos';
|
import OrgUserRepos from './org-user-repos';
|
||||||
import OrgUserSharedRepos from './org-user-shared-repos';
|
import OrgUserSharedRepos from './org-user-shared-repos';
|
||||||
@@ -34,7 +34,7 @@ class Org extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isSidePanelClosed: false,
|
isSidePanelClosed: false,
|
||||||
currentTab: 'users',
|
currentTab: 'users'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,8 @@ class Org extends React.Component {
|
|||||||
<div className="main-panel o-hidden">
|
<div className="main-panel o-hidden">
|
||||||
<Router className="reach-router">
|
<Router className="reach-router">
|
||||||
<OrgInfo path={siteRoot + 'org/orgmanage'}/>
|
<OrgInfo path={siteRoot + 'org/orgmanage'}/>
|
||||||
<OrgUsers path={siteRoot + 'org/useradmin'} currentTab={currentTab} tabItemClick={this.tabItemClick}/>
|
<OrgUsers path={siteRoot + 'org/useradmin'} />
|
||||||
|
<OrgAdmins path={siteRoot + 'org/useradmin/admins/'} />
|
||||||
<OrgUserProfile path={siteRoot + 'org/useradmin/info/:email/'} />
|
<OrgUserProfile path={siteRoot + 'org/useradmin/info/:email/'} />
|
||||||
<OrgUserRepos path={siteRoot + 'org/useradmin/info/:email/repos/'} />
|
<OrgUserRepos path={siteRoot + 'org/useradmin/info/:email/repos/'} />
|
||||||
<OrgUserSharedRepos path={siteRoot + 'org/useradmin/info/:email/shared-repos/'} />
|
<OrgUserSharedRepos path={siteRoot + 'org/useradmin/info/:email/shared-repos/'} />
|
||||||
|
@@ -10,7 +10,7 @@ class MainPanelTopbar extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="main-panel-north border-left-show">
|
<div className={`main-panel-north ${this.props.children ? 'border-left-show' : ''}`}>
|
||||||
<div className="cur-view-toolbar">
|
<div className="cur-view-toolbar">
|
||||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||||
<div className="operation">
|
<div className="operation">
|
||||||
|
@@ -10,7 +10,7 @@ const propTypes = {
|
|||||||
toggleDelete: PropTypes.func.isRequired,
|
toggleDelete: PropTypes.func.isRequired,
|
||||||
toggleRevokeAdmin: PropTypes.func.isRequired,
|
toggleRevokeAdmin: PropTypes.func.isRequired,
|
||||||
orgAdminUsers: PropTypes.array.isRequired,
|
orgAdminUsers: PropTypes.array.isRequired,
|
||||||
initOrgAdmin: PropTypes.func.isRequired,
|
initOrgAdmin: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
class OrgAdminList extends React.Component {
|
class OrgAdminList extends React.Component {
|
||||||
@@ -18,7 +18,7 @@ class OrgAdminList extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isItemFreezed: false,
|
isItemFreezed: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@ class OrgAdminList extends React.Component {
|
|||||||
<tr>
|
<tr>
|
||||||
<th width="30%">{gettext('Name')}</th>
|
<th width="30%">{gettext('Name')}</th>
|
||||||
<th width="15%">{gettext('Status')}</th>
|
<th width="15%">{gettext('Status')}</th>
|
||||||
<th width="15%">{gettext('Space Used')}</th>
|
<th width="20%">{gettext('Space Used')} / {gettext('Quota')}</th>
|
||||||
<th width="20%">{gettext('Create At / Last Login')}</th>
|
<th width="25%">{gettext('Created At')} / {gettext('Last Login')}</th>
|
||||||
<th width="20%" className="text-center">{gettext('Operations')}</th>
|
<th width="10%">{/*Operations*/}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -55,7 +55,7 @@ class OrgAdminList extends React.Component {
|
|||||||
<UserItem
|
<UserItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
user={item}
|
user={item}
|
||||||
currentTab={this.props.currentTab}
|
currentTab="admins"
|
||||||
isItemFreezed={this.state.isItemFreezed}
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
toggleDelete={this.props.toggleDelete}
|
toggleDelete={this.props.toggleDelete}
|
||||||
toggleRevokeAdmin={this.props.toggleRevokeAdmin}
|
toggleRevokeAdmin={this.props.toggleRevokeAdmin}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import React, { Fragment, Component } from 'react';
|
import React, { Fragment, Component } from 'react';
|
||||||
|
import { navigate } from '@reach/router';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||||
import MainPanelTopbar from './main-panel-topbar';
|
import MainPanelTopbar from './main-panel-topbar';
|
||||||
@@ -18,17 +19,26 @@ class OrgLibraries extends Component {
|
|||||||
page: 1,
|
page: 1,
|
||||||
pageNext: false,
|
pageNext: false,
|
||||||
orgRepos: [],
|
orgRepos: [],
|
||||||
|
sortBy: '',
|
||||||
isItemFreezed: false
|
isItemFreezed: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
let page = this.state.page;
|
let urlParams = (new URL(window.location)).searchParams;
|
||||||
this.initData(page);
|
const { page, /*currentPage = 1, perPage, */sortBy } = this.state;
|
||||||
|
this.setState({
|
||||||
|
sortBy: urlParams.get('order_by') || sortBy,
|
||||||
|
//perPage: parseInt(urlParams.get('per_page') || perPage),
|
||||||
|
//currentPage: parseInt(urlParams.get('page') || currentPage)
|
||||||
|
page: parseInt(urlParams.get('page') || page)
|
||||||
|
}, () => {
|
||||||
|
this.listRepos(this.state.page);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initData = (page) => {
|
listRepos = (page) => {
|
||||||
seafileAPI.orgAdminListOrgRepos(orgID, page).then(res => {
|
seafileAPI.orgAdminListOrgRepos(orgID, page, this.state.sortBy).then(res => {
|
||||||
let orgRepos = res.data.repo_list.map(item => {
|
let orgRepos = res.data.repo_list.map(item => {
|
||||||
return new OrgAdminRepo(item);
|
return new OrgAdminRepo(item);
|
||||||
});
|
});
|
||||||
@@ -54,7 +64,7 @@ class OrgLibraries extends Component {
|
|||||||
} else {
|
} else {
|
||||||
page = page - 1;
|
page = page - 1;
|
||||||
}
|
}
|
||||||
this.initData(page);
|
this.listRepos(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFreezedItem = () => {
|
onFreezedItem = () => {
|
||||||
@@ -91,11 +101,39 @@ class OrgLibraries extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortItems = (sortBy) => {
|
||||||
|
this.setState({
|
||||||
|
page: 1,
|
||||||
|
sortBy: sortBy
|
||||||
|
}, () => {
|
||||||
|
let url = new URL(location.href);
|
||||||
|
let searchParams = new URLSearchParams(url.search);
|
||||||
|
const { page, sortBy } = this.state;
|
||||||
|
searchParams.set('page', page);
|
||||||
|
searchParams.set('order_by', sortBy);
|
||||||
|
url.search = searchParams.toString();
|
||||||
|
navigate(url.toString());
|
||||||
|
this.listRepos(page);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByFileCount = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.sortItems('file_count');
|
||||||
|
}
|
||||||
|
|
||||||
|
sortBySize = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.sortItems('size');
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let repos = this.state.orgRepos;
|
const { orgRepos, sortBy } = this.state;
|
||||||
|
const initialSortIcon = <span className="fas fa-sort"></span>;
|
||||||
|
const sortIcon = <span className="fas fa-caret-down"></span>;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<MainPanelTopbar/>
|
<MainPanelTopbar />
|
||||||
<div className="main-panel-center flex-row">
|
<div className="main-panel-center flex-row">
|
||||||
<div className="cur-view-container">
|
<div className="cur-view-container">
|
||||||
<div className="cur-view-path">
|
<div className="cur-view-path">
|
||||||
@@ -105,15 +143,19 @@ class OrgLibraries extends Component {
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="4%"></th>
|
<th width="5%">{/*icon*/}</th>
|
||||||
<th width="27%">{gettext('Name')}</th>
|
<th width="25%">{gettext('Name')}</th>
|
||||||
<th width="30%">ID</th>
|
<th width="15%">
|
||||||
<th width="24%">{gettext('Owner')}</th>
|
<a className="d-inline-block table-sort-op" href="#" onClick={this.sortByFileCount}>{gettext('Files')} {sortBy == 'file_count' ? sortIcon : initialSortIcon}</a>{' / '}
|
||||||
<th width="15%" className="text-center">{gettext('Operations')}</th>
|
<a className="d-inline-block table-sort-op" href="#" onClick={this.sortBySize}>{gettext('Size')} {sortBy == 'size' ? sortIcon : initialSortIcon}</a>
|
||||||
|
</th>
|
||||||
|
<th width="32%">ID</th>
|
||||||
|
<th width="18%">{gettext('Owner')}</th>
|
||||||
|
<th width="5%">{/*Operations*/}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{repos.map(item => {
|
{orgRepos.map(item => {
|
||||||
return (
|
return (
|
||||||
<RepoItem
|
<RepoItem
|
||||||
key={item.repoID}
|
key={item.repoID}
|
||||||
@@ -259,7 +301,8 @@ class RepoItem extends React.Component {
|
|||||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<td>{this.renderLibIcon(repo)}</td>
|
<td>{this.renderLibIcon(repo)}</td>
|
||||||
<td>{repo.repoName}</td>
|
<td>{repo.repoName}</td>
|
||||||
<td style={{'fontSize': '11px'}}>{repo.repoID}</td>
|
<td>{`${repo.file_count} / ${Utils.bytesToSize(repo.size)}`}</td>
|
||||||
|
<td>{repo.repoID}</td>
|
||||||
<td><a href={this.renderRepoOwnerHref(repo)}>{repo.ownerName}</a></td>
|
<td><a href={this.renderRepoOwnerHref(repo)}>{repo.ownerName}</a></td>
|
||||||
<td className="text-center cursor-pointer">
|
<td className="text-center cursor-pointer">
|
||||||
{isOperationMenuShow &&
|
{isOperationMenuShow &&
|
||||||
|
@@ -130,7 +130,7 @@ class UserItem extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<td>
|
<td>
|
||||||
<a href={href} className="font-weight-normal">{user.name}</a>
|
<a href={href}>{user.name}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<UserStatusEditor
|
<UserStatusEditor
|
||||||
@@ -141,8 +141,12 @@ class UserItem extends React.Component {
|
|||||||
onStatusChanged={this.changeStatus}
|
onStatusChanged={this.changeStatus}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>{user.quota ? user.self_usage + ' / ' + user.quota : user.self_usage}</td>
|
<td>{`${user.self_usage} / ${user.quota || '--'}`}</td>
|
||||||
<td style={{'fontSize': '11px'}}>{user.ctime} / {user.last_login ? user.last_login : '--'}</td>
|
<td>
|
||||||
|
{user.ctime} /
|
||||||
|
<br />
|
||||||
|
{user.last_login ? user.last_login : '--'}
|
||||||
|
</td>
|
||||||
<td className="text-center cursor-pointer">
|
<td className="text-center cursor-pointer">
|
||||||
{isOperationMenuShow && (
|
{isOperationMenuShow && (
|
||||||
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
||||||
|
114
frontend/src/pages/org-admin/org-users-admins.js
Normal file
114
frontend/src/pages/org-admin/org-users-admins.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import Nav from './org-users-nav';
|
||||||
|
import OrgAdminList from './org-admin-list';
|
||||||
|
import MainPanelTopbar from './main-panel-topbar';
|
||||||
|
import AddOrgAdminDialog from '../../components/dialog/org-add-admin-dialog';
|
||||||
|
import ModalPortal from '../../components/modal-portal';
|
||||||
|
import toaster from '../../components/toast';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import OrgUserInfo from '../../models/org-user';
|
||||||
|
import { gettext, orgID } from '../../utils/constants';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
|
class OrgUsers extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
orgAdminUsers: [],
|
||||||
|
isShowAddOrgAdminDialog: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddOrgAdmin = () => {
|
||||||
|
this.setState({isShowAddOrgAdminDialog: !this.state.isShowAddOrgAdminDialog});
|
||||||
|
}
|
||||||
|
|
||||||
|
initOrgAdmin = () => {
|
||||||
|
seafileAPI.orgAdminListOrgUsers(orgID, true).then(res => {
|
||||||
|
let userList = res.data.user_list.map(item => {
|
||||||
|
return new OrgUserInfo(item);
|
||||||
|
});
|
||||||
|
this.setState({orgAdminUsers: userList});
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOrgAdminDelete = (email) => {
|
||||||
|
seafileAPI.orgAdminDeleteOrgUser(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);
|
||||||
|
toaster.success(msg);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleRevokeAdmin = (email) => {
|
||||||
|
seafileAPI.orgAdminSetOrgAdmin(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', res.data.name);
|
||||||
|
toaster.success(msg);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddedOrgAdmin = (userInfo) => {
|
||||||
|
this.state.orgAdminUsers.unshift(userInfo);
|
||||||
|
this.setState({
|
||||||
|
orgAdminUsers: this.state.orgAdminUsers
|
||||||
|
});
|
||||||
|
let msg = gettext('Successfully set %s as admin.');
|
||||||
|
msg = msg.replace('%s', userInfo.email);
|
||||||
|
toaster.success(msg);
|
||||||
|
this.toggleAddOrgAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const topBtn = 'btn btn-secondary operation-item';
|
||||||
|
let topbarChildren;
|
||||||
|
topbarChildren = (
|
||||||
|
<Fragment>
|
||||||
|
<button className={topBtn} title={gettext('Add admin')} onClick={this.toggleAddOrgAdmin}>
|
||||||
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add admin')}
|
||||||
|
</button>
|
||||||
|
{this.state.isShowAddOrgAdminDialog &&
|
||||||
|
<ModalPortal>
|
||||||
|
<AddOrgAdminDialog toggle={this.toggleAddOrgAdmin} onAddedOrgAdmin={this.onAddedOrgAdmin}/>
|
||||||
|
</ModalPortal>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar children={topbarChildren}/>
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<Nav currentItem="admins" />
|
||||||
|
<OrgAdminList
|
||||||
|
currentTab="admins"
|
||||||
|
toggleDelete={this.toggleOrgAdminDelete}
|
||||||
|
toggleRevokeAdmin={this.toggleRevokeAdmin}
|
||||||
|
orgAdminUsers={this.state.orgAdminUsers}
|
||||||
|
initOrgAdmin={this.initOrgAdmin}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgUsers;
|
@@ -4,7 +4,6 @@ import { gettext } from '../../utils/constants';
|
|||||||
import UserItem from './org-user-item';
|
import UserItem from './org-user-item';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
currentTab: PropTypes.string.isRequired,
|
|
||||||
initOrgUsersData: PropTypes.func.isRequired,
|
initOrgUsersData: PropTypes.func.isRequired,
|
||||||
toggleDelete: PropTypes.func.isRequired,
|
toggleDelete: PropTypes.func.isRequired,
|
||||||
orgUsers: PropTypes.array.isRequired,
|
orgUsers: PropTypes.array.isRequired,
|
||||||
@@ -17,14 +16,10 @@ class OrgUsersList extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isItemFreezed: false,
|
isItemFreezed: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.initOrgUsersData(this.props.page);
|
|
||||||
}
|
|
||||||
|
|
||||||
onFreezedItem = () => {
|
onFreezedItem = () => {
|
||||||
this.setState({isItemFreezed: true});
|
this.setState({isItemFreezed: true});
|
||||||
}
|
}
|
||||||
@@ -46,7 +41,20 @@ class OrgUsersList extends React.Component {
|
|||||||
this.props.initOrgUsersData(page);
|
this.props.initOrgUsersData(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortByQuotaUsage = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.sortByQuotaUsage();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { sortBy, sortOrder } = this.props;
|
||||||
|
let sortIcon;
|
||||||
|
if (sortBy == '') {
|
||||||
|
// initial sort icon
|
||||||
|
sortIcon = <span className="fas fa-sort"></span>;
|
||||||
|
} else {
|
||||||
|
sortIcon = <span className={`fas ${sortOrder == 'asc' ? 'fa-caret-up' : 'fa-caret-down'}`}></span>;
|
||||||
|
}
|
||||||
let { orgUsers, page, pageNext } = this.props;
|
let { orgUsers, page, pageNext } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="cur-view-content">
|
<div className="cur-view-content">
|
||||||
@@ -55,9 +63,11 @@ class OrgUsersList extends React.Component {
|
|||||||
<tr>
|
<tr>
|
||||||
<th width="30%">{gettext('Name')}</th>
|
<th width="30%">{gettext('Name')}</th>
|
||||||
<th width="15%">{gettext('Status')}</th>
|
<th width="15%">{gettext('Status')}</th>
|
||||||
<th width="15%">{gettext('Space Used')}</th>
|
<th width="20%">
|
||||||
<th width="20%">{gettext('Create At / Last Login')}</th>
|
<a className="d-inline-block table-sort-op" href="#" onClick={this.sortByQuotaUsage}>{gettext('Space Used')} {sortIcon}</a> / {gettext('Quota')}
|
||||||
<th width="20%" className="text-center">{gettext('Operations')}</th>
|
</th>
|
||||||
|
<th width="25%">{gettext('Created At')} / {gettext('Last Login')}</th>
|
||||||
|
<th width="10%">{/*Operations*/}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -66,7 +76,7 @@ class OrgUsersList extends React.Component {
|
|||||||
<UserItem
|
<UserItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
user={item}
|
user={item}
|
||||||
currentTab={this.props.currentTab}
|
currentTab="users"
|
||||||
isItemFreezed={this.state.isItemFreezed}
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
toggleDelete={this.props.toggleDelete}
|
toggleDelete={this.props.toggleDelete}
|
||||||
onFreezedItem={this.onFreezedItem}
|
onFreezedItem={this.onFreezedItem}
|
||||||
|
40
frontend/src/pages/org-admin/org-users-nav.js
Normal file
40
frontend/src/pages/org-admin/org-users-nav.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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: 'all', urlPart: 'useradmin', text: gettext('All')},
|
||||||
|
{name: 'admins', urlPart: 'useradmin/admins', text: gettext('Admin')}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { currentItem } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="cur-view-path tab-nav-container">
|
||||||
|
<ul className="nav">
|
||||||
|
{this.navItems.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<li className="nav-item" key={index}>
|
||||||
|
<Link to={`${siteRoot}org/${item.urlPart}/`} className={`nav-link${currentItem == item.name ? ' active' : ''}`}>{item.text}</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Nav.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default Nav;
|
167
frontend/src/pages/org-admin/org-users-users.js
Normal file
167
frontend/src/pages/org-admin/org-users-users.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import { navigate } from '@reach/router';
|
||||||
|
import Nav from './org-users-nav';
|
||||||
|
import OrgUsersList from './org-users-list';
|
||||||
|
import MainPanelTopbar from './main-panel-topbar';
|
||||||
|
import ModalPortal from '../../components/modal-portal';
|
||||||
|
import AddOrgUserDialog from '../../components/dialog/org-add-user-dialog';
|
||||||
|
import InviteUserDialog from '../../components/dialog/org-admin-invite-user-dialog';
|
||||||
|
import toaster from '../../components/toast';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import OrgUserInfo from '../../models/org-user';
|
||||||
|
import { gettext, invitationLink, orgID } from '../../utils/constants';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
|
class OrgUsers extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
orgUsers: [],
|
||||||
|
page: 1,
|
||||||
|
pageNext: false,
|
||||||
|
sortBy: '',
|
||||||
|
sortOrder: 'asc',
|
||||||
|
isShowAddOrgUserDialog: false,
|
||||||
|
isInviteUserDialogOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
let urlParams = (new URL(window.location)).searchParams;
|
||||||
|
const { page, sortBy, sortOrder } = this.state;
|
||||||
|
this.setState({
|
||||||
|
/*
|
||||||
|
perPage: parseInt(urlParams.get('per_page') || perPage),
|
||||||
|
currentPage: parseInt(urlParams.get('page') || currentPage),
|
||||||
|
*/
|
||||||
|
page: parseInt(urlParams.get('page') || page),
|
||||||
|
sortBy: urlParams.get('order_by') || sortBy,
|
||||||
|
sortOrder: urlParams.get('direction') || sortOrder
|
||||||
|
}, () => {
|
||||||
|
this.initOrgUsersData(this.state.page);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByQuotaUsage = () => {
|
||||||
|
this.setState({
|
||||||
|
sortBy: 'quota_usage',
|
||||||
|
sortOrder: this.state.sortOrder == 'asc' ? 'desc' : 'asc',
|
||||||
|
page: 1
|
||||||
|
}, () => {
|
||||||
|
let url = new URL(location.href);
|
||||||
|
let searchParams = new URLSearchParams(url.search);
|
||||||
|
const { page, sortBy, sortOrder } = this.state;
|
||||||
|
searchParams.set('page', page);
|
||||||
|
searchParams.set('order_by', sortBy);
|
||||||
|
searchParams.set('direction', sortOrder);
|
||||||
|
url.search = searchParams.toString();
|
||||||
|
navigate(url.toString());
|
||||||
|
this.initOrgUsersData(page);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAddOrgUser = () => {
|
||||||
|
this.setState({isShowAddOrgUserDialog: !this.state.isShowAddOrgUserDialog});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleInviteUserDialog = () => {
|
||||||
|
this.setState({isInviteUserDialogOpen: !this.state.isInviteUserDialogOpen});
|
||||||
|
}
|
||||||
|
|
||||||
|
initOrgUsersData = (page) => {
|
||||||
|
const { sortBy, sortOrder } = this.state;
|
||||||
|
seafileAPI.orgAdminListOrgUsers(orgID, '', page, sortBy, sortOrder).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,
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrgUser = (email, name, password) => {
|
||||||
|
seafileAPI.orgAdminAddOrgUser(orgID, email, name, password).then(res => {
|
||||||
|
let userInfo = new OrgUserInfo(res.data);
|
||||||
|
this.state.orgUsers.unshift(userInfo);
|
||||||
|
this.setState({
|
||||||
|
orgUsers: this.state.orgUsers
|
||||||
|
});
|
||||||
|
this.toggleAddOrgUser();
|
||||||
|
let msg = gettext('successfully added user %s.');
|
||||||
|
msg = msg.replace('%s', email);
|
||||||
|
toaster.success(msg);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
this.toggleAddOrgUser();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOrgUsersDelete = (email) => {
|
||||||
|
seafileAPI.orgAdminDeleteOrgUser(orgID, email).then(res => {
|
||||||
|
let users = this.state.orgUsers.filter(item => item.email != email);
|
||||||
|
this.setState({orgUsers: users});
|
||||||
|
let msg = gettext('Successfully deleted %s');
|
||||||
|
msg = msg.replace('%s', email);
|
||||||
|
toaster.success(msg);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const topBtn = 'btn btn-secondary operation-item';
|
||||||
|
let topbarChildren;
|
||||||
|
topbarChildren = (
|
||||||
|
<Fragment>
|
||||||
|
<button className={topBtn} title={gettext('Add user')} onClick={this.toggleAddOrgUser}>
|
||||||
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add user')}</button>
|
||||||
|
{invitationLink &&
|
||||||
|
<button className={topBtn} title={gettext('Invite user')} onClick={this.toggleInviteUserDialog}>
|
||||||
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite user')}</button>
|
||||||
|
}
|
||||||
|
{this.state.isShowAddOrgUserDialog &&
|
||||||
|
<ModalPortal>
|
||||||
|
<AddOrgUserDialog handleSubmit={this.addOrgUser} toggle={this.toggleAddOrgUser}/>
|
||||||
|
</ModalPortal>
|
||||||
|
}
|
||||||
|
{this.state.isInviteUserDialogOpen &&
|
||||||
|
<ModalPortal>
|
||||||
|
<InviteUserDialog invitationLink={invitationLink} toggle={this.toggleInviteUserDialog}/>
|
||||||
|
</ModalPortal>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<MainPanelTopbar children={topbarChildren}/>
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<Nav currentItem="all" />
|
||||||
|
<OrgUsersList
|
||||||
|
initOrgUsersData={this.initOrgUsersData}
|
||||||
|
toggleDelete={this.toggleOrgUsersDelete}
|
||||||
|
orgUsers={this.state.orgUsers}
|
||||||
|
page={this.state.page}
|
||||||
|
pageNext={this.state.pageNext}
|
||||||
|
sortBy={this.state.sortBy}
|
||||||
|
sortOrder={this.state.sortOrder}
|
||||||
|
sortByQuotaUsage={this.sortByQuotaUsage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrgUsers;
|
@@ -1,232 +0,0 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import OrgUsersList from './org-users-list';
|
|
||||||
import OrgAdminList from './org-admin-list';
|
|
||||||
import MainPanelTopbar from './main-panel-topbar';
|
|
||||||
import AddOrgAdminDialog from '../../components/dialog/org-add-admin-dialog';
|
|
||||||
import ModalPortal from '../../components/modal-portal';
|
|
||||||
import AddOrgUserDialog from '../../components/dialog/org-add-user-dialog';
|
|
||||||
import InviteUserDialog from '../../components/dialog/org-admin-invite-user-dialog';
|
|
||||||
import toaster from '../../components/toast';
|
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
|
||||||
import OrgUserInfo from '../../models/org-user';
|
|
||||||
import { gettext, invitationLink, orgID } from '../../utils/constants';
|
|
||||||
import { Utils } from '../../utils/utils';
|
|
||||||
|
|
||||||
class OrgUsers extends Component {
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
orgAdminUsers: [],
|
|
||||||
isShowAddOrgAdminDialog: false,
|
|
||||||
orgUsers: [],
|
|
||||||
page: 1,
|
|
||||||
pageNext: false,
|
|
||||||
isShowAddOrgUserDialog: false,
|
|
||||||
isInviteUserDialogOpen: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
tabItemClick = (param) => {
|
|
||||||
this.props.tabItemClick(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleAddOrgAdmin = () => {
|
|
||||||
this.setState({isShowAddOrgAdminDialog: !this.state.isShowAddOrgAdminDialog});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleAddOrgUser = () => {
|
|
||||||
this.setState({isShowAddOrgUserDialog: !this.state.isShowAddOrgUserDialog});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleInviteUserDialog = () => {
|
|
||||||
this.setState({isInviteUserDialogOpen: !this.state.isInviteUserDialogOpen});
|
|
||||||
}
|
|
||||||
|
|
||||||
initOrgUsersData = (page) => {
|
|
||||||
seafileAPI.orgAdminListOrgUsers(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,
|
|
||||||
});
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addOrgUser = (email, name, password) => {
|
|
||||||
seafileAPI.orgAdminAddOrgUser(orgID, email, name, password).then(res => {
|
|
||||||
let userInfo = new OrgUserInfo(res.data);
|
|
||||||
this.state.orgUsers.unshift(userInfo);
|
|
||||||
this.setState({
|
|
||||||
orgUsers: this.state.orgUsers
|
|
||||||
});
|
|
||||||
this.toggleAddOrgUser();
|
|
||||||
let msg = gettext('successfully added user %s.');
|
|
||||||
msg = msg.replace('%s', email);
|
|
||||||
toaster.success(msg);
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
this.toggleAddOrgUser();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleOrgUsersDelete = (email) => {
|
|
||||||
seafileAPI.orgAdminDeleteOrgUser(orgID, email).then(res => {
|
|
||||||
let users = this.state.orgUsers.filter(item => item.email != email);
|
|
||||||
this.setState({orgUsers: users});
|
|
||||||
let msg = gettext('Successfully deleted %s');
|
|
||||||
msg = msg.replace('%s', email);
|
|
||||||
toaster.success(msg);
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initOrgAdmin = () => {
|
|
||||||
seafileAPI.orgAdminListOrgUsers(orgID, true).then(res => {
|
|
||||||
let userList = res.data.user_list.map(item => {
|
|
||||||
return new OrgUserInfo(item);
|
|
||||||
});
|
|
||||||
this.setState({orgAdminUsers: userList});
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleOrgAdminDelete = (email) => {
|
|
||||||
seafileAPI.orgAdminDeleteOrgUser(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);
|
|
||||||
toaster.success(msg);
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleRevokeAdmin = (email) => {
|
|
||||||
seafileAPI.orgAdminSetOrgAdmin(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', res.data.name);
|
|
||||||
toaster.success(msg);
|
|
||||||
}).catch(error => {
|
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddedOrgAdmin = (userInfo) => {
|
|
||||||
this.state.orgAdminUsers.unshift(userInfo);
|
|
||||||
this.setState({
|
|
||||||
orgAdminUsers: this.state.orgAdminUsers
|
|
||||||
});
|
|
||||||
let msg = gettext('Successfully set %s as admin.');
|
|
||||||
msg = msg.replace('%s', userInfo.email);
|
|
||||||
toaster.success(msg);
|
|
||||||
this.toggleAddOrgAdmin();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const topBtn = 'btn btn-secondary operation-item';
|
|
||||||
let topbarChildren;
|
|
||||||
if (this.props.currentTab === 'admins') {
|
|
||||||
topbarChildren = (
|
|
||||||
<Fragment>
|
|
||||||
<button className={topBtn} title={gettext('Add admin')} onClick={this.toggleAddOrgAdmin}>
|
|
||||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add admin')}
|
|
||||||
</button>
|
|
||||||
{this.state.isShowAddOrgAdminDialog &&
|
|
||||||
<ModalPortal>
|
|
||||||
<AddOrgAdminDialog toggle={this.toggleAddOrgAdmin} onAddedOrgAdmin={this.onAddedOrgAdmin}/>
|
|
||||||
</ModalPortal>
|
|
||||||
}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
} else if (this.props.currentTab === 'users') {
|
|
||||||
topbarChildren = (
|
|
||||||
<Fragment>
|
|
||||||
<button className={topBtn} title={gettext('Add user')} onClick={this.toggleAddOrgUser}>
|
|
||||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add user')}</button>
|
|
||||||
{invitationLink &&
|
|
||||||
<button className={topBtn} title={gettext('Invite user')} onClick={this.toggleInviteUserDialog}>
|
|
||||||
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite user')}</button>
|
|
||||||
}
|
|
||||||
{this.state.isShowAddOrgUserDialog &&
|
|
||||||
<ModalPortal>
|
|
||||||
<AddOrgUserDialog handleSubmit={this.addOrgUser} toggle={this.toggleAddOrgUser}/>
|
|
||||||
</ModalPortal>
|
|
||||||
}
|
|
||||||
{this.state.isInviteUserDialogOpen &&
|
|
||||||
<ModalPortal>
|
|
||||||
<InviteUserDialog invitationLink={invitationLink} toggle={this.toggleInviteUserDialog}/>
|
|
||||||
</ModalPortal>
|
|
||||||
}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<MainPanelTopbar children={topbarChildren}/>
|
|
||||||
<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')}>
|
|
||||||
<span className={`nav-link ${this.props.currentTab === 'users' ? 'active': ''}`}>{gettext('All')}</span>
|
|
||||||
</li>
|
|
||||||
<li className="nav-item" onClick={() => this.tabItemClick('admins')}>
|
|
||||||
<span className={`nav-link ${this.props.currentTab === 'admins' ? 'active': ''}`} >{gettext('Admin')}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{this.props.currentTab === 'users' &&
|
|
||||||
<OrgUsersList
|
|
||||||
currentTab={this.props.currentTab}
|
|
||||||
initOrgUsersData={this.initOrgUsersData}
|
|
||||||
toggleDelete={this.toggleOrgUsersDelete}
|
|
||||||
orgUsers={this.state.orgUsers}
|
|
||||||
page={this.state.page}
|
|
||||||
pageNext={this.state.pageNext}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{this.props.currentTab === 'admins' &&
|
|
||||||
<OrgAdminList
|
|
||||||
currentTab={this.props.currentTab}
|
|
||||||
toggleDelete={this.toggleOrgAdminDelete}
|
|
||||||
toggleRevokeAdmin={this.toggleRevokeAdmin}
|
|
||||||
orgAdminUsers={this.state.orgAdminUsers}
|
|
||||||
initOrgAdmin={this.initOrgAdmin}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
currentTab: PropTypes.string.isRequired,
|
|
||||||
tabItemClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
OrgUsers.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default OrgUsers;
|
|
Reference in New Issue
Block a user