mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-18 16:36:15 +00:00
Merge branch '8.0' into master
This commit is contained in:
62
frontend/package-lock.json
generated
62
frontend/package-lock.json
generated
@@ -3762,11 +3762,11 @@
|
||||
}
|
||||
},
|
||||
"@seafile/seafile-editor": {
|
||||
"version": "0.3.63",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/seafile-editor/-/seafile-editor-0.3.63.tgz",
|
||||
"integrity": "sha512-mzqdQlyfs6MpN4n263GtKI6nlYwbVAVO+fj+Q28CiZH8uMwMsa6e9KcQn0yyJWAvxEy6i/L1kWpgztcZFV42Ow==",
|
||||
"version": "0.3.72",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/seafile-editor/-/seafile-editor-0.3.72.tgz",
|
||||
"integrity": "sha512-fV7Arc6aIAGKCxbQ+QNhLiieafJefQsCnaXrGlyjWzLF46T8ivRMPZxfik6YZqQLt165JtJ1SET6dEgK7QBlYA==",
|
||||
"requires": {
|
||||
"@seafile/slate-react": "^0.54.12",
|
||||
"@seafile/slate-react": "^0.54.13",
|
||||
"codemirror": "^5.37.0",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"deep-equal": "^1.0.1",
|
||||
@@ -3856,9 +3856,9 @@
|
||||
}
|
||||
},
|
||||
"@seafile/slate-react": {
|
||||
"version": "0.54.12",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/slate-react/-/slate-react-0.54.12.tgz",
|
||||
"integrity": "sha512-AYV7u8zq/ztnJuMY5Vic9uaPeAJUVGXt9zkTU05Jh/j9eEr4rzuGUbJIoRVHbTFckAOn+spvUNUpCYME5OF+gg==",
|
||||
"version": "0.54.13",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/slate-react/-/slate-react-0.54.13.tgz",
|
||||
"integrity": "sha512-NjaY2EXwgMMJFJp2523w2+hK2S+dAWbHCMXadhuuM0S7ZzrerO86XA2N4ZBsaTwQqzJiEJBjg6+I/szD0HaNEA==",
|
||||
"requires": {
|
||||
"@types/debounce": "^1.2.0",
|
||||
"@types/debug": "^4.1.5",
|
||||
@@ -4270,9 +4270,9 @@
|
||||
"integrity": "sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw=="
|
||||
},
|
||||
"@types/debug": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
|
||||
"integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ=="
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.6.tgz",
|
||||
"integrity": "sha512-7fDOJFA/x8B+sO1901BmHlf5dE1cxBU8mRXj8QOEDnn16hhGJv/IHxJtZhvsabZsIMn0eLIyeOKAeqSNJJYTpA=="
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "7.2.8",
|
||||
@@ -4321,9 +4321,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/is-hotkey": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.2.tgz",
|
||||
"integrity": "sha512-SUw9LpI3AIwbRNXS7FYy9AlXrTPIdBZGI7y4XxfIEYqgSW1UfFCUM9cMwHE/yCfTl0qeI0UQ/q8TdIxsIFgKPg=="
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.3.tgz",
|
||||
"integrity": "sha512-Hz+eHHpMWLBX1CpDXSuQre9nYXN2e2VGVHvkkldxDzo9eFtRpHm5iOlJlZvnNGvele5584cUSkRnFRQb+Wcu0w=="
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
@@ -4372,9 +4372,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.168",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
|
||||
"integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q=="
|
||||
"version": "4.14.171",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz",
|
||||
"integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg=="
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.4",
|
||||
@@ -6956,9 +6956,9 @@
|
||||
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="
|
||||
},
|
||||
"commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.0.0.tgz",
|
||||
"integrity": "sha512-Xvf85aAtu6v22+E5hfVoLHqyul/jyxh91zvqk/ioJTQuJR7Z78n7H558vMPKanPSRgIEeZemT92I2g9Y8LPbSQ=="
|
||||
},
|
||||
"common-tags": {
|
||||
"version": "1.8.0",
|
||||
@@ -7433,7 +7433,7 @@
|
||||
},
|
||||
"css-in-js-utils": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
|
||||
"resolved": "http://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
|
||||
"integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==",
|
||||
"requires": {
|
||||
"hyphenate-style-name": "^1.0.2",
|
||||
@@ -13957,13 +13957,14 @@
|
||||
}
|
||||
},
|
||||
"mathjax-full": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.1.2.tgz",
|
||||
"integrity": "sha512-jFCwRFdFwIOa8J7r6VZT0AIv9ZwbLQ9aPc9YZp695NTvv7XKU2NunJodA+zDWzElIFJ7mTsImyfe5R3QyRNZjw==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.0.tgz",
|
||||
"integrity": "sha512-D2EBNvUG+mJyhn+M1C858k0f2Fc4KxXvbEX2WCMXroV10212JwfYqaBJ336ECBSz5X9L5LRoamxb7AJtg3KaJA==",
|
||||
"requires": {
|
||||
"esm": "^3.2.25",
|
||||
"mhchemparser": "^4.1.0",
|
||||
"mj-context-menu": "^0.6.1",
|
||||
"speech-rule-engine": "^3.1.1"
|
||||
"speech-rule-engine": "^3.3.3"
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
@@ -14077,6 +14078,11 @@
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
|
||||
"dev": true
|
||||
},
|
||||
"mhchemparser": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz",
|
||||
"integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA=="
|
||||
},
|
||||
"microevent.ts": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz",
|
||||
@@ -19187,11 +19193,11 @@
|
||||
}
|
||||
},
|
||||
"speech-rule-engine": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.1.1.tgz",
|
||||
"integrity": "sha512-FGX8B44yI3yGhmcw8nZ/by2ffUlZG6m5b/O3RULXsSiwhL/evL+jwQ6BXQxV3gGtOYptOFalTVCAFknAJgBKAg==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.3.3.tgz",
|
||||
"integrity": "sha512-0exWw+0XauLjat+f/aFeo5T8SiDsO1JtwpY3qgJE4cWt+yL/Stl0WP4VNDWdh7lzGkubUD9lWP4J1ASnORXfyQ==",
|
||||
"requires": {
|
||||
"commander": "^6.0.0",
|
||||
"commander": ">=7.0.0",
|
||||
"wicked-good-xpath": "^1.3.0",
|
||||
"xmldom-sre": "^0.1.31"
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"@seafile/react-image-lightbox": "0.0.1",
|
||||
"@seafile/resumablejs": "1.1.16",
|
||||
"@seafile/seafile-calendar": "0.0.12",
|
||||
"@seafile/seafile-editor": "^0.3.63",
|
||||
"@seafile/seafile-editor": "^0.3.72",
|
||||
"MD5": "^1.3.0",
|
||||
"chart.js": "2.9.4",
|
||||
"classnames": "^2.2.6",
|
||||
@@ -35,7 +35,7 @@
|
||||
"react-responsive": "^6.1.2",
|
||||
"react-select": "^2.4.1",
|
||||
"reactstrap": "^6.4.0",
|
||||
"seafile-js": "0.2.173",
|
||||
"seafile-js": "0.2.175",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"unified": "^7.0.0",
|
||||
"url-parse": "^1.4.3",
|
||||
|
@@ -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);
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
@@ -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;
|
@@ -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}
|
||||
|
@@ -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 {
|
||||
|
@@ -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/'} />
|
||||
|
@@ -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>
|
||||
|
279
frontend/src/pages/org-admin/org-groups-search-groups.js
Normal file
279
frontend/src/pages/org-admin/org-groups-search-groups.js
Normal 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;
|
@@ -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">
|
||||
|
186
frontend/src/pages/org-admin/org-users-search-users.js
Normal file
186
frontend/src/pages/org-admin/org-users-search-users.js
Normal 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;
|
@@ -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" />
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user