diff --git a/frontend/src/models/org-admin-repo.js b/frontend/src/models/org-admin-repo.js index e5974d4654..f45ae943f6 100644 --- a/frontend/src/models/org-admin-repo.js +++ b/frontend/src/models/org-admin-repo.js @@ -4,6 +4,8 @@ class OrgAdminRepo { this.repoName = object.repo_name; this.ownerName = object.owner_name; this.ownerEmail = object.owner_email; + this.size = object.size; + this.file_count = object.file_count; this.encrypted = object.encrypted; this.isDepartmentRepo = object.is_department_repo; this.groupID = object.group_id; diff --git a/frontend/src/pages/org-admin/index.js b/frontend/src/pages/org-admin/index.js index a62a2ebad4..a6b3fc5287 100644 --- a/frontend/src/pages/org-admin/index.js +++ b/frontend/src/pages/org-admin/index.js @@ -1,10 +1,10 @@ -// 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 OrgUsers from './org-users'; +import OrgUsers from './org-users-users'; +import OrgAdmins from './org-users-admins'; import OrgUserProfile from './org-user-profile'; import OrgUserRepos from './org-user-repos'; import OrgUserSharedRepos from './org-user-shared-repos'; @@ -34,7 +34,7 @@ class Org extends React.Component { super(props); this.state = { isSidePanelClosed: false, - currentTab: 'users', + currentTab: 'users' }; } @@ -70,7 +70,8 @@ class Org extends React.Component {
- + + diff --git a/frontend/src/pages/org-admin/main-panel-topbar.js b/frontend/src/pages/org-admin/main-panel-topbar.js index 988e5a610d..2c0acc636f 100644 --- a/frontend/src/pages/org-admin/main-panel-topbar.js +++ b/frontend/src/pages/org-admin/main-panel-topbar.js @@ -10,7 +10,7 @@ class MainPanelTopbar extends Component { render() { return ( -
+
diff --git a/frontend/src/pages/org-admin/org-admin-list.js b/frontend/src/pages/org-admin/org-admin-list.js index 04666c77fa..838fe02693 100644 --- a/frontend/src/pages/org-admin/org-admin-list.js +++ b/frontend/src/pages/org-admin/org-admin-list.js @@ -10,7 +10,7 @@ const propTypes = { toggleDelete: PropTypes.func.isRequired, toggleRevokeAdmin: PropTypes.func.isRequired, orgAdminUsers: PropTypes.array.isRequired, - initOrgAdmin: PropTypes.func.isRequired, + initOrgAdmin: PropTypes.func.isRequired }; class OrgAdminList extends React.Component { @@ -18,7 +18,7 @@ class OrgAdminList extends React.Component { constructor(props) { super(props); this.state = { - isItemFreezed: false, + isItemFreezed: false }; } @@ -44,9 +44,9 @@ class OrgAdminList extends React.Component { {gettext('Name')} {gettext('Status')} - {gettext('Space Used')} - {gettext('Create At / Last Login')} - {gettext('Operations')} + {gettext('Space Used')} / {gettext('Quota')} + {gettext('Created At')} / {gettext('Last Login')} + {/*Operations*/} @@ -55,7 +55,7 @@ class OrgAdminList extends React.Component { { + this.listRepos(this.state.page); + }); } - initData = (page) => { - seafileAPI.orgAdminListOrgRepos(orgID, page).then(res => { + listRepos = (page) => { + seafileAPI.orgAdminListOrgRepos(orgID, page, this.state.sortBy).then(res => { let orgRepos = res.data.repo_list.map(item => { return new OrgAdminRepo(item); }); @@ -54,7 +64,7 @@ class OrgLibraries extends Component { } else { page = page - 1; } - this.initData(page); + this.listRepos(page); } 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() { - let repos = this.state.orgRepos; + const { orgRepos, sortBy } = this.state; + const initialSortIcon = ; + const sortIcon = ; return ( - +
@@ -105,15 +143,19 @@ class OrgLibraries extends Component { - - - - - + + + + + + - {repos.map(item => { + {orgRepos.map(item => { return ( - + + - - + + - - - + + + @@ -66,7 +76,7 @@ class OrgUsersList extends React.Component { +
    + {this.navItems.map((item, index) => { + return ( +
  • + {item.text} +
  • + ); + })} +
+ + ); + } +} + +Nav.propTypes = propTypes; + +export default Nav; diff --git a/frontend/src/pages/org-admin/org-users-users.js b/frontend/src/pages/org-admin/org-users-users.js new file mode 100644 index 0000000000..4ea4d543a0 --- /dev/null +++ b/frontend/src/pages/org-admin/org-users-users.js @@ -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 = ( + + + {invitationLink && + + } + {this.state.isShowAddOrgUserDialog && + + + + } + {this.state.isInviteUserDialogOpen && + + + + } + + ); + + return ( + + +
+
+
+
+
+ ); + } +} + +export default OrgUsers; diff --git a/frontend/src/pages/org-admin/org-users.js b/frontend/src/pages/org-admin/org-users.js deleted file mode 100644 index 728a019ac3..0000000000 --- a/frontend/src/pages/org-admin/org-users.js +++ /dev/null @@ -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 = ( - - - {this.state.isShowAddOrgAdminDialog && - - - - } - - ); - } else if (this.props.currentTab === 'users') { - topbarChildren = ( - - - {invitationLink && - - } - {this.state.isShowAddOrgUserDialog && - - - - } - {this.state.isInviteUserDialogOpen && - - - - } - - ); - } - - return ( - - -
-
-
-
    -
  • this.tabItemClick('users')}> - {gettext('All')} -
  • -
  • this.tabItemClick('admins')}> - {gettext('Admin')} -
  • -
-
- {this.props.currentTab === 'users' && - - } - {this.props.currentTab === 'admins' && - - } -
-
-
- ); - } -} - -const propTypes = { - currentTab: PropTypes.string.isRequired, - tabItemClick: PropTypes.func.isRequired, -}; - -OrgUsers.propTypes = propTypes; - -export default OrgUsers;
{gettext('Name')}ID{gettext('Owner')}{gettext('Operations')}{/*icon*/}{gettext('Name')} + {gettext('Files')} {sortBy == 'file_count' ? sortIcon : initialSortIcon}{' / '} + {gettext('Size')} {sortBy == 'size' ? sortIcon : initialSortIcon} + ID{gettext('Owner')}{/*Operations*/}
{this.renderLibIcon(repo)} {repo.repoName}{repo.repoID}{`${repo.file_count} / ${Utils.bytesToSize(repo.size)}`}{repo.repoID} {repo.ownerName} {isOperationMenuShow && diff --git a/frontend/src/pages/org-admin/org-user-item.js b/frontend/src/pages/org-admin/org-user-item.js index 39a3e0bdf0..bd1ddfe85d 100644 --- a/frontend/src/pages/org-admin/org-user-item.js +++ b/frontend/src/pages/org-admin/org-user-item.js @@ -130,7 +130,7 @@ class UserItem extends React.Component { return (
- {user.name} + {user.name} {user.quota ? user.self_usage + ' / ' + user.quota : user.self_usage}{user.ctime} / {user.last_login ? user.last_login : '--'}{`${user.self_usage} / ${user.quota || '--'}`} + {user.ctime} / +
+ {user.last_login ? user.last_login : '--'} +
{isOperationMenuShow && ( diff --git a/frontend/src/pages/org-admin/org-users-admins.js b/frontend/src/pages/org-admin/org-users-admins.js new file mode 100644 index 0000000000..47417e85d7 --- /dev/null +++ b/frontend/src/pages/org-admin/org-users-admins.js @@ -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 = ( + + + {this.state.isShowAddOrgAdminDialog && + + + + } + + ); + + return ( + + +
+
+
+
+
+ ); + } +} + +export default OrgUsers; diff --git a/frontend/src/pages/org-admin/org-users-list.js b/frontend/src/pages/org-admin/org-users-list.js index 4e575f5af9..b63717442a 100644 --- a/frontend/src/pages/org-admin/org-users-list.js +++ b/frontend/src/pages/org-admin/org-users-list.js @@ -4,7 +4,6 @@ import { gettext } from '../../utils/constants'; import UserItem from './org-user-item'; const propTypes = { - currentTab: PropTypes.string.isRequired, initOrgUsersData: PropTypes.func.isRequired, toggleDelete: PropTypes.func.isRequired, orgUsers: PropTypes.array.isRequired, @@ -17,14 +16,10 @@ class OrgUsersList extends React.Component { constructor(props) { super(props); this.state = { - isItemFreezed: false, + isItemFreezed: false }; } - componentDidMount() { - this.props.initOrgUsersData(this.props.page); - } - onFreezedItem = () => { this.setState({isItemFreezed: true}); } @@ -46,7 +41,20 @@ class OrgUsersList extends React.Component { this.props.initOrgUsersData(page); } + sortByQuotaUsage = (e) => { + e.preventDefault(); + this.props.sortByQuotaUsage(); + } + render() { + const { sortBy, sortOrder } = this.props; + let sortIcon; + if (sortBy == '') { + // initial sort icon + sortIcon = ; + } else { + sortIcon = ; + } let { orgUsers, page, pageNext } = this.props; return (
@@ -55,9 +63,11 @@ class OrgUsersList extends React.Component {
{gettext('Name')} {gettext('Status')}{gettext('Space Used')}{gettext('Create At / Last Login')}{gettext('Operations')} + {gettext('Space Used')} {sortIcon} / {gettext('Quota')} + {gettext('Created At')} / {gettext('Last Login')}{/*Operations*/}