1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-21 11:27:18 +00:00

Merge branch '8.0' into master

This commit is contained in:
lian
2021-08-06 13:07:36 +08:00
27 changed files with 876 additions and 63 deletions

View File

@@ -61,13 +61,21 @@ class ZipDownloadDialog extends React.Component {
const zipToken = this.state.zipToken;
seafileAPI.queryZipProgress(zipToken).then((res) => {
const data = res.data;
this.setState({
zipProgress: data.total == 0 ? '100%' : (data.zipped/data.total*100).toFixed(2) + '%'
});
if (data['total'] == data['zipped']) {
if (data.failed == 1) {
clearInterval(interval);
this.props.toggleDialog();
location.href = `${fileServerRoot}zip/${zipToken}`;
this.setState({
isLoading: false,
errorMsg: data.failed_reason
});
} else {
this.setState({
zipProgress: data.total == 0 ? '100%' : (data.zipped/data.total*100).toFixed(2) + '%'
});
if (data['total'] == data['zipped']) {
clearInterval(interval);
this.props.toggleDialog();
location.href = `${fileServerRoot}zip/${zipToken}`;
}
}
}).catch((error) => {
clearInterval(interval);

View File

@@ -107,7 +107,7 @@ class FileToolbar extends React.Component {
{(canEditFile && !err) &&
( this.props.isSaving ?
<button type={'button'} className={'btn btn-icon btn-secondary btn-active'}>
<button type={'button'} aria-label={gettext('Saving...')} className={'btn btn-icon btn-secondary btn-active'}>
<i className={'fa fa-spin fa-spinner'}/></button> :
(
this.props.needSave ?
@@ -149,7 +149,7 @@ class FileToolbar extends React.Component {
/>
)}
<ButtonDropdown isOpen={moreDropdownOpen} toggle={this.toggleMoreOpMenu}>
<DropdownToggle>
<DropdownToggle aria-label={gettext('More Operations')}>
<span className="fas fa-ellipsis-v"></span>
</DropdownToggle>
<DropdownMenu right={true}>
@@ -172,8 +172,8 @@ class FileToolbar extends React.Component {
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.toggle} className="d-block d-md-none">
<ButtonGroup >
{(canEditFile && !err) &&
( this.props.isSaving ?
<button type={'button'} className={'btn btn-icon btn-secondary btn-active'}>
(this.props.isSaving ?
<button type={'button'} aria-label={gettext('Saving...')} className={'btn btn-icon btn-secondary btn-active'}>
<i className={'fa fa-spin fa-spinner'}/></button> :
(
this.props.needSave ?
@@ -192,7 +192,7 @@ class FileToolbar extends React.Component {
)}
</ButtonGroup>
<DropdownToggle className="sf2-icon-more mx-1">
<DropdownToggle className="sf2-icon-more mx-1" aria-label={gettext('More Operations')}>
</DropdownToggle>
<DropdownMenu right={true}>
<DropdownItem>

View File

@@ -47,6 +47,7 @@ class IconButton extends React.Component {
className={className}
tag="a"
href={this.props.href}
aria-label={this.props.text}
>
{btnContent}
</Button>
@@ -57,6 +58,7 @@ class IconButton extends React.Component {
id={this.props.id}
className={className}
onClick={this.props.onClick}
aria-label={this.props.text}
>
{btnContent}
</Button>

View File

@@ -95,6 +95,7 @@ class SelectEditor extends React.Component {
className="permission-editor-select"
classNamePrefix="permission-editor"
placeholder={this.props.translateOption(currentOption)}
value={currentOption}
onChange={this.onOptionChanged}
captureMenuScroll={false}
/>

View File

@@ -0,0 +1,41 @@
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,
statusOptions: PropTypes.array.isRequired,
currentStatus: PropTypes.string.isRequired,
onStatusChanged: PropTypes.func.isRequired
};
class SysAdminUserMembershipEditor extends React.Component {
translateStatus = (status) => {
switch (status) {
case 'is_org_staff':
return gettext('Admin');
case 'not_is_org_staff':
return gettext('Member');
}
}
render() {
return (
<SelectEditor
isTextMode={this.props.isTextMode}
isEditIconShow={this.props.isEditIconShow}
options={this.props.statusOptions}
currentOption={this.props.currentStatus}
onOptionChanged={this.props.onStatusChanged}
translateOption={this.translateStatus}
/>
);
}
}
SysAdminUserMembershipEditor.propTypes = propTypes;
export default SysAdminUserMembershipEditor;

View File

@@ -72,7 +72,7 @@ class MoreMenu extends React.PureComponent {
const isSmall = this.props.isSmallScreen;
return (
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.dropdownToggle} direction="down" className="mx-1">
<DropdownToggle id="moreButton">
<DropdownToggle id="moreButton" aria-label={gettext('More Operations')}>
<i className="fa fa-ellipsis-v"/>
<Tooltip toggle={this.tooltipToggle} delay={{show: 0, hide: 0}} target="moreButton" placement='bottom' isOpen={this.state.tooltipOpen}>{gettext('More')}
</Tooltip>
@@ -179,11 +179,11 @@ class MarkdownViewerToolbar extends React.Component {
<IconButton id={'shareBtn'} text={gettext('Share')} icon={'fa fa-share-alt'}
onMouseDown={this.props.toggleShareLinkDialog}/>
}
{ saving ?
<button type={'button'} className={'btn btn-icon btn-secondary btn-active'}>
{saving ?
<button type={'button'} aria-label={gettext('Saving...')} className={'btn btn-icon btn-secondary btn-active'}>
<i className={'fa fa-spin fa-spinner'}/></button>
:
<IconButton text={gettext('Save')} id={'saveButton'} icon={'fa fa-save'} disabled={!contentChanged}
<IconButton text={gettext('Save')} id={'saveButton'} icon={'fa fa-save'} disabled={!contentChanged}
onMouseDown={window.seafileEditor && window.seafileEditor.onRichEditorSave} isActive={contentChanged}/>
}
{canDownloadFile && (
@@ -224,7 +224,7 @@ class MarkdownViewerToolbar extends React.Component {
<div className="topbar-btn-container">
<ButtonGroup>
{saving ?
<button type={'button'} className={'btn btn-icon btn-secondary btn-active'}>
<button type={'button'} aria-label={gettext('Saving...')} className={'btn btn-icon btn-secondary btn-active'}>
<i className={'fa fa-spin fa-spinner'}/></button>
:
<IconButton text={gettext('Save')} id={'saveButton'} icon={'fa fa-save'} disabled={!contentChanged}

View File

@@ -56,7 +56,12 @@ img[src=""] {
display:flex;
flex-direction:column;
overflow:hidden;
z-index: 1051; /* for mobile */
}
@media (max-width: 767px) {
.wiki-side-panel {
z-index: 1051;
}
}
.wiki-main-panel {

View File

@@ -4,11 +4,13 @@ import { Router } from '@reach/router';
import { siteRoot } from '../../utils/constants';
import SidePanel from './side-panel';
import OrgUsers from './org-users-users';
import OrgUsersSearchUsers from './org-users-search-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';
import OrgGroups from './org-groups';
import OrgGroupsSearchGroups from './org-groups-search-groups';
import OrgGroupInfo from './org-group-info';
import OrgGroupRepos from './org-group-repos';
import OrgGroupMembers from './org-group-members';
@@ -68,11 +70,13 @@ class Org extends React.Component {
<Router className="reach-router">
<OrgInfo path={siteRoot + 'org/orgmanage'}/>
<OrgUsers path={siteRoot + 'org/useradmin'} />
<OrgUsersSearchUsers path={siteRoot + 'org/useradmin/search-users'} />
<OrgAdmins path={siteRoot + 'org/useradmin/admins/'} />
<OrgUserProfile path={siteRoot + 'org/useradmin/info/:email/'} />
<OrgUserRepos path={siteRoot + 'org/useradmin/info/:email/repos/'} />
<OrgUserSharedRepos path={siteRoot + 'org/useradmin/info/:email/shared-repos/'} />
<OrgGroups path={siteRoot + 'org/groupadmin'} />
<OrgGroupsSearchGroups path={siteRoot + 'org/groupadmin/search-groups'} />
<OrgGroupInfo path={siteRoot + 'org/groupadmin/:groupID/'} />
<OrgGroupRepos path={siteRoot + 'org/groupadmin/:groupID/repos/'} />
<OrgGroupMembers path={siteRoot + 'org/groupadmin/:groupID/members/'} />

View File

@@ -18,6 +18,7 @@ class MainPanelTopbar extends Component {
</div>
</div>
<div className="common-toolbar">
{this.props.search && this.props.search}
<Account isAdminPanel={true}/>
</div>
</div>

View File

@@ -0,0 +1,279 @@
import React, { Component, Fragment } from 'react';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { Button, Form, FormGroup, Input, Col } from 'reactstrap';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, orgID, siteRoot } from '../../utils/constants';
import toaster from '../../components/toast';
import OrgGroupInfo from '../../models/org-group';
class GroupItem extends React.Component {
constructor(props) {
super(props);
this.state = {
highlight: false,
showMenu: false,
isItemMenuShow: false
};
}
onMouseEnter = () => {
if (!this.props.isItemFreezed) {
this.setState({
showMenu: true,
highlight: true,
});
}
}
onMouseLeave = () => {
if (!this.props.isItemFreezed) {
this.setState({
showMenu: false,
highlight: false
});
}
}
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();
}
}
);
}
toggleDelete = () => {
this.props.deleteGroupItem(this.props.group);
}
renderGroupHref = (group) => {
let groupInfoHref;
if (group.creatorName == 'system admin') {
groupInfoHref = siteRoot + 'org/departmentadmin/groups/' + group.id + '/';
} else {
groupInfoHref = siteRoot + 'org/groupadmin/' + group.id + '/';
}
return groupInfoHref;
}
renderGroupCreator = (group) => {
let userInfoHref = siteRoot + 'org/useradmin/info/' + group.creatorEmail + '/';
if (group.creatorName == 'system admin') {
return (
<td> -- </td>
);
} else {
return(
<td>
<a href={userInfoHref} className="font-weight-normal">{group.creatorName}</a>
</td>
);
}
}
render() {
let { group } = this.props;
let isOperationMenuShow = (group.creatorName != 'system admin') && this.state.showMenu;
return (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td>
<a href={this.renderGroupHref(group)} className="font-weight-normal">{group.groupName}</a>
</td>
{this.renderGroupCreator(group)}
<td>{group.ctime}</td>
<td className="text-center cursor-pointer">
{isOperationMenuShow &&
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
<DropdownToggle
tag="a"
className="attr-action-icon 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>
</DropdownMenu>
</Dropdown>
}
</td>
</tr>
);
}
}
class OrgGroupsSearchGroupsResult extends React.Component {
constructor(props) {
super(props);
this.state = {
isItemFreezed: false
};
}
onFreezedItem = () => {
this.setState({isItemFreezed: true});
}
onUnfreezedItem = () => {
this.setState({isItemFreezed: false});
}
render() {
let { orgGroups } = this.props;
return (
<div className="cur-view-content">
<table>
<thead>
<tr>
<th width="30%">{gettext('Name')}</th>
<th width="35%">{gettext('Creator')}</th>
<th width="23%">{gettext('Created At')}</th>
<th width="12%" className="text-center">{gettext('Operations')}</th>
</tr>
</thead>
<tbody>
{orgGroups.map(item => {
return (
<GroupItem
key={item.id}
group={item}
isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
deleteGroupItem={this.props.toggleDelete}
/>
);
})}
</tbody>
</table>
</div>
);
}
}
class OrgGroupsSearchGroups extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
orgGroups: [],
isSubmitBtnActive: false,
loading: true,
errorMsg: '',
};
}
componentDidMount () {
let params = (new URL(document.location)).searchParams;
this.setState({
query: params.get('query') || '',
}, () => {this.getItems();});
}
getItems = () => {
seafileAPI.orgAdminSearchGroup(orgID, this.state.query.trim()).then(res => {
let groupList = res.data.group_list.map(item => {
return new OrgGroupInfo(item);
});
this.setState({
orgGroups: groupList,
loading: false,
});
}).catch((error) => {
this.setState({
loading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
deleteGroupItem = (group) => {
seafileAPI.orgAdminDeleteOrgGroup(orgID, group.id).then(res => {
this.setState({
orgGroups: this.state.orgGroups.filter(item => item.id != group.id)
});
let msg = gettext('Successfully deleted {name}');
msg = msg.replace('{name}', group.groupName);
toaster.success(msg);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
handleInputChange = (e) => {
this.setState({
query: e.target.value
}, this.checkSubmitBtnActive);
}
checkSubmitBtnActive = () => {
const { query } = this.state;
this.setState({
isSubmitBtnActive: query.trim()
});
}
render() {
const { query, isSubmitBtnActive } = this.state;
return (
<Fragment>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading">{gettext('Groups')}</h3>
</div>
<div className="cur-view-content">
<div className="mt-4 mb-6">
<h4 className="border-bottom font-weight-normal mb-2 pb-1">{gettext('Search Groups')}</h4>
<Form>
<FormGroup row>
<Col sm={5}>
<Input type="text" name="query" value={query} placeholder={gettext('Search groups')} onChange={this.handleInputChange} />
</Col>
</FormGroup>
<FormGroup row>
<Col sm={{size: 5}}>
<button className="btn btn-outline-primary" disabled={!isSubmitBtnActive} onClick={this.getItems}>{gettext('Submit')}</button>
</Col>
</FormGroup>
</Form>
</div>
<div className="mt-4 mb-6">
<h4 className="border-bottom font-weight-normal mb-2 pb-1">{gettext('Result')}</h4>
<OrgGroupsSearchGroupsResult
toggleDelete={this.deleteGroupItem}
orgGroups={this.state.orgGroups}
/>
</div>
</div>
</div>
</div>
</Fragment>
);
}
}
export default OrgGroupsSearchGroups;

View File

@@ -1,4 +1,5 @@
import React, { Component, Fragment } from 'react';
import { navigate } from '@reach/router';
import PropTypes from 'prop-types';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { siteRoot, gettext, orgID } from '../../utils/constants';
@@ -8,6 +9,55 @@ import toaster from '../../components/toast';
import OrgGroupInfo from '../../models/org-group';
import MainPanelTopbar from './main-panel-topbar';
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleInputChange = (e) => {
this.setState({
value: e.target.value
});
}
handleKeyPress = (e) => {
if (e.key == 'Enter') {
e.preventDefault();
this.handleSubmit();
}
}
handleSubmit = () => {
const value = this.state.value.trim();
if (!value) {
return false;
}
this.props.submit(value);
}
render() {
return (
<div className="input-icon">
<i className="d-flex input-icon-addon fas fa-search"></i>
<input
type="text"
className="form-control search-input h-6 mr-1"
style={{width: '15rem'}}
placeholder={this.props.placeholder}
value={this.state.value}
onChange={this.handleInputChange}
onKeyPress={this.handleKeyPress}
autoComplete="off"
/>
</div>
);
}
}
class OrgGroups extends Component {
constructor(props) {
@@ -77,11 +127,22 @@ class OrgGroups extends Component {
});
}
searchItems = (keyword) => {
navigate(`${siteRoot}org/groupadmin/search-groups/?query=${encodeURIComponent(keyword)}`);
}
getSearch = () => {
return <Search
placeholder={gettext('Search groups by name')}
submit={this.searchItems}
/>;
}
render() {
let groups = this.state.orgGroups;
return (
<Fragment>
<MainPanelTopbar/>
<MainPanelTopbar search={this.getSearch()}/>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path">

View File

@@ -0,0 +1,186 @@
import React, { Component, Fragment } from 'react';
import { Button, Form, FormGroup, Input, Col } from 'reactstrap';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, orgID } from '../../utils/constants';
import toaster from '../../components/toast';
import UserItem from './org-user-item';
import OrgUserInfo from '../../models/org-user';
class OrgUsersSearchUsersResult extends React.Component {
constructor(props) {
super(props);
this.state = {
isItemFreezed: false
};
}
onFreezedItem = () => {
this.setState({isItemFreezed: true});
}
onUnfreezedItem = () => {
this.setState({isItemFreezed: false});
}
render() {
let { orgUsers } = this.props;
return (
<div className="cur-view-content">
<table>
<thead>
<tr>
<th width="30%">{gettext('Name')}</th>
<th width="15%">{gettext('Status')}</th>
<th width="20%">
<a className="d-inline-block table-sort-op" href="#" >{gettext('Space Used')}</a> / {gettext('Quota')}
</th>
<th width="25%">{gettext('Created At')} / {gettext('Last Login')}</th>
<th width="10%">{/*Operations*/}</th>
</tr>
</thead>
<tbody>
{orgUsers.map((item, index) => {
return (
<UserItem
key={index}
user={item}
currentTab="users"
isItemFreezed={this.state.isItemFreezed}
toggleDelete={this.props.toggleDelete}
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
/>
);})}
</tbody>
</table>
</div>
);
}
}
class OrgUsersSearchUsers extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
orgUsers: [],
org_id: '',
isSubmitBtnActive: false,
loading: true,
errorMsg: '',
};
}
componentDidMount () {
let params = (new URL(document.location)).searchParams;
this.setState({
query: params.get('query') || '',
}, () => {this.getItems();});
}
getItems = () => {
seafileAPI.orgAdminSearchUser(orgID, this.state.query.trim()).then(res => {
let userList = res.data.user_list.map(item => {
return new OrgUserInfo(item);
});
this.setState({
orgUsers: userList,
loading: false,
});
}).catch((error) => {
this.setState({
loading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
deleteUser = (email) => {
seafileAPI.orgAdminDeleteOrgUser(orgID, email).then(res => {
let newUserList = this.state.orgUsers.filter(item => {
return item.email != email;
});
this.setState({orgUsers: newUserList});
toaster.success(gettext('Successfully deleted 1 item.'));
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
updateUser = (email, key, value) => {
seafileAPI.sysAdminUpdateUser(email, key, value).then(res => {
let newUserList = this.state.orgUsers.map(item => {
if (item.email == email) {
item[key]= res.data[key];
}
return item;
});
this.setState({orgUsers: newUserList});
const msg = (key == 'is_active' && value) ?
res.data.update_status_tip : gettext('Edit succeeded');
toaster.success(msg);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
handleInputChange = (e) => {
this.setState({
query: e.target.value
}, this.checkSubmitBtnActive);
}
checkSubmitBtnActive = () => {
const { query } = this.state;
this.setState({
isSubmitBtnActive: query.trim()
});
}
render() {
const { query, isSubmitBtnActive } = this.state;
return (
<Fragment>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading">{gettext('Users')}</h3>
</div>
<div className="cur-view-content">
<div className="mt-4 mb-6">
<h4 className="border-bottom font-weight-normal mb-2 pb-1">{gettext('Search Users')}</h4>
<Form>
<FormGroup row>
<Col sm={5}>
<Input type="text" name="query" value={query} placeholder={gettext('Search users')} onChange={this.handleInputChange} />
</Col>
</FormGroup>
<FormGroup row>
<Col sm={{size: 5}}>
<button className="btn btn-outline-primary" disabled={!isSubmitBtnActive} onClick={this.getItems}>{gettext('Submit')}</button>
</Col>
</FormGroup>
</Form>
</div>
<div className="mt-4 mb-6">
<h4 className="border-bottom font-weight-normal mb-2 pb-1">{gettext('Result')}</h4>
<OrgUsersSearchUsersResult
toggleDelete={this.deleteUser}
orgUsers={this.state.orgUsers}
/>
</div>
</div>
</div>
</div>
</Fragment>
);
}
}
export default OrgUsersSearchUsers;

View File

@@ -9,9 +9,58 @@ import InviteUserDialog from '../../components/dialog/org-admin-invite-user-dial
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 { gettext, invitationLink, orgID, siteRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils';
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleInputChange = (e) => {
this.setState({
value: e.target.value
});
}
handleKeyPress = (e) => {
if (e.key == 'Enter') {
e.preventDefault();
this.handleSubmit();
}
}
handleSubmit = () => {
const value = this.state.value.trim();
if (!value) {
return false;
}
this.props.submit(value);
}
render() {
return (
<div className="input-icon">
<i className="d-flex input-icon-addon fas fa-search"></i>
<input
type="text"
className="form-control search-input h-6 mr-1"
style={{width: '15rem'}}
placeholder={this.props.placeholder}
value={this.state.value}
onChange={this.handleInputChange}
onKeyPress={this.handleKeyPress}
autoComplete="off"
/>
</div>
);
}
}
class OrgUsers extends Component {
constructor(props) {
@@ -117,6 +166,17 @@ class OrgUsers extends Component {
});
}
searchItems = (keyword) => {
navigate(`${siteRoot}org/useradmin/search-users/?query=${encodeURIComponent(keyword)}`);
}
getSearch = () => {
return <Search
placeholder={gettext('Search users')}
submit={this.searchItems}
/>;
}
render() {
const topBtn = 'btn btn-secondary operation-item';
let topbarChildren;
@@ -143,7 +203,7 @@ class OrgUsers extends Component {
return (
<Fragment>
<MainPanelTopbar children={topbarChildren}/>
<MainPanelTopbar children={topbarChildren} search={this.getSearch()}/>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<Nav currentItem="all" />

View File

@@ -8,6 +8,7 @@ import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import Loading from '../../../components/loading';
import SysAdminUserStatusEditor from '../../../components/select-editor/sysadmin-user-status-editor';
import SysAdminUserMembershipEditor from '../../../components/select-editor/sysadmin-user-membership-editor';
import SysAdminAddUserDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-user-dialog';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
import OpMenu from '../../../components/dialog/op-menu';
@@ -50,9 +51,10 @@ class Content extends Component {
<thead>
<tr>
<th width="25%">{gettext('Name')}</th>
<th width="20%">{gettext('Status')}</th>
<th width="20%">{gettext('Space Used')}</th>
<th width="30%">{gettext('Created At')}{' / '}{gettext('Last Login')}</th>
<th width="15%">{gettext('Status')}</th>
<th width="15%">{gettext('Membership')}</th>
<th width="15%">{gettext('Space Used')}</th>
<th width="25%">{gettext('Created At')}{' / '}{gettext('Last Login')}</th>
<th width="5%">{/* Operations */}</th>
</tr>
</thead>
@@ -65,6 +67,7 @@ class Content extends Component {
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
updateStatus={this.props.updateStatus}
updateMembership={this.props.updateMembership}
deleteUser={this.props.deleteUser}
/>);
})}
@@ -146,6 +149,10 @@ class Item extends Component {
this.props.updateStatus(this.props.item.email, statusValue);
}
updateMembership= (membershipValue) => {
this.props.updateMembership(this.props.item.email, membershipValue);
}
deleteUser = () => {
const { item } = this.props;
this.props.deleteUser(item.org_id, item.email);
@@ -195,6 +202,15 @@ class Item extends Component {
onStatusChanged={this.updateStatus}
/>
</td>
<td>
<SysAdminUserMembershipEditor
isTextMode={true}
isEditIconShow={isOpIconShown}
currentStatus={item.is_org_staff ? 'is_org_staff' : 'not_is_org_staff'}
statusOptions={['is_org_staff', 'not_is_org_staff']}
onStatusChanged={this.updateMembership}
/>
</td>
<td>{`${Utils.bytesToSize(item.quota_usage)} / ${item.quota_total > 0 ? Utils.bytesToSize(item.quota_total) : '--'}`}</td>
<td>
{moment(item.create_time).format('YYYY-MM-DD HH:mm:ss')}{' / '}{item.last_login ? moment(item.last_login).fromNow() : '--'}
@@ -311,6 +327,22 @@ class OrgUsers extends Component {
});
}
updateMembership = (email, membershipValue) => {
const isOrgStaff = membershipValue == 'is_org_staff';
seafileAPI.sysAdminUpdateOrgUser(this.props.orgID, email, 'is_org_staff', isOrgStaff).then(res => {
let newUserList = this.state.userList.map(item => {
if (item.email == email) {
item.is_org_staff = res.data.is_org_staff;
}
return item;
});
this.setState({userList: newUserList});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
const { isAddUserDialogOpen, orgName } = this.state;
return (
@@ -331,6 +363,7 @@ class OrgUsers extends Component {
errorMsg={this.state.errorMsg}
items={this.state.userList}
updateStatus={this.updateStatus}
updateMembership={this.updateMembership}
deleteUser={this.deleteUser}
/>
</div>