mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 15:38:15 +00:00
sysadmin recontruct devices page (#3925)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||
|
||||
const propTypes = {
|
||||
unlinkDevice: PropTypes.func.isRequired,
|
||||
toggleDialog: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class SysAdminUnlinkDevice extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
inputChecked: false
|
||||
};
|
||||
}
|
||||
|
||||
handleInputChange = (e) => {
|
||||
this.setState({
|
||||
inputChecked: e.target.checked
|
||||
});
|
||||
}
|
||||
|
||||
unlinkDevice = () => {
|
||||
this.props.toggleDialog();
|
||||
this.props.unlinkDevice(this.state.inputChecked);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { inputChecked } = this.state;
|
||||
const toggle = this.props.toggleDialog;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={toggle}>
|
||||
<ModalHeader toggle={toggle}>{gettext('Unlink device')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>{gettext('Are you sure you want to unlink this device?')}</p>
|
||||
<div className="d-flex align-items-center">
|
||||
<input id="delete-files" className="mr-1" type="checkbox" checked={inputChecked} onChange={this.handleInputChange} />
|
||||
<label htmlFor="delete-files" className="m-0">{gettext('Delete files from this device the next time it comes online.')}</label>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={toggle}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.unlinkDevice}>{gettext('Unlink')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SysAdminUnlinkDevice.propTypes = propTypes;
|
||||
|
||||
export default SysAdminUnlinkDevice;
|
58
frontend/src/components/paginator.js
Normal file
58
frontend/src/components/paginator.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../utils/constants';
|
||||
import { Label } from 'reactstrap';
|
||||
|
||||
|
||||
const propTypes = {
|
||||
gotoPreviousPage: PropTypes.func.isRequired,
|
||||
gotoNextPage: PropTypes.func.isRequired,
|
||||
currentPage: PropTypes.number.isRequired,
|
||||
hasNextPage: PropTypes.bool.isRequired,
|
||||
canResetPerPage: PropTypes.bool.isRequired,
|
||||
resetPerPage: PropTypes.func
|
||||
};
|
||||
|
||||
class Paginator extends Component {
|
||||
|
||||
resetPerPage = (perPage) => {
|
||||
this.props.resetPerPage(perPage);
|
||||
}
|
||||
|
||||
goToPrevious = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.gotoPreviousPage();
|
||||
}
|
||||
|
||||
goToNext = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.gotoNextPage();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="my-6 text-center">
|
||||
{this.props.currentPage != 1 &&
|
||||
<a href="#" onClick={this.goToPrevious}>{gettext('Previous')}</a>
|
||||
}
|
||||
{this.props.hasNextPage &&
|
||||
<a href="#" onClick={this.goToNext} className="ml-4">{gettext('Next')}</a>
|
||||
}
|
||||
</div>
|
||||
{this.props.canResetPerPage &&
|
||||
<div>
|
||||
{gettext('Per page:')}{' '}
|
||||
<Label onClick={() => {return this.resetPerPage(25);}}>25</Label>
|
||||
<Label onClick={() => {return this.resetPerPage(50);}}>50</Label>
|
||||
<Label onClick={() => {return this.resetPerPage(100);}}>100</Label>
|
||||
</div>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Paginator.propTypes = propTypes;
|
||||
|
||||
export default Paginator;
|
@@ -22,6 +22,5 @@
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
.btn {
|
||||
margin-top: 1rem;
|
||||
min-width: 60px;
|
||||
}
|
||||
}
|
||||
|
29
frontend/src/pages/sys-admin/devices/desktop-devices.js
Normal file
29
frontend/src/pages/sys-admin/devices/desktop-devices.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import DevicesNav from './devices-nav';
|
||||
import DevicesByPlatform from './devices-by-platform';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
|
||||
class DesktopDevices extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar />
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<DevicesNav currentItem="desktop" />
|
||||
<DevicesByPlatform
|
||||
devicesPlatform={'desktop'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DesktopDevices;
|
213
frontend/src/pages/sys-admin/devices/devices-by-platform.js
Normal file
213
frontend/src/pages/sys-admin/devices/devices-by-platform.js
Normal file
@@ -0,0 +1,213 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { loginUrl, gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import moment from 'moment';
|
||||
import Loading from '../../../components/loading';
|
||||
import Paginator from '../../../components/paginator';
|
||||
import SysAdminUnlinkDevice from '../../../components/dialog/sysadmin-dialog/sysadmin-unlink-device-dialog';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
getPreviousPageDevicesList = () => {
|
||||
this.props.getDevicesListByPage(this.props.pageInfo.current_page - 1);
|
||||
}
|
||||
|
||||
getNextPageDevicesList = () => {
|
||||
this.props.getDevicesListByPage(this.props.pageInfo.current_page + 1);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items, pageInfo } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No connected devices')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="19%">{gettext('User')}</th>
|
||||
<th width="19%">{gettext('Platform')}{' / '}{gettext('Version')}</th>
|
||||
<th width="19%">{gettext('Device Name')}</th>
|
||||
<th width="19%">{gettext('IP')}</th>
|
||||
<th width="19%">{gettext('Last Access')}</th>
|
||||
<th width="5%">{/*Operations*/}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item key={index} item={item} />);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<Paginator
|
||||
gotoPreviousPage={this.getPreviousPageDevicesList}
|
||||
gotoNextPage={this.getNextPageDevicesList}
|
||||
currentPage={pageInfo.current_page}
|
||||
hasNextPage={pageInfo.has_next_page}
|
||||
canResetPerPage={false}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
unlinked: false,
|
||||
isOpIconShown: false,
|
||||
isUnlinkDeviceDialogOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({isOpIconShown: true});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({isOpIconShown: false});
|
||||
}
|
||||
|
||||
handleUnlink = (e) => {
|
||||
e.preventDefault();
|
||||
if (this.props.item.is_desktop_client) {
|
||||
this.toggleUnlinkDeviceDialog();
|
||||
} else {
|
||||
this.unlinkDevice(true);
|
||||
}
|
||||
}
|
||||
|
||||
toggleUnlinkDeviceDialog = () => {
|
||||
this.setState({isUnlinkDeviceDialogOpen: !this.state.isUnlinkDeviceDialogOpen});
|
||||
}
|
||||
|
||||
unlinkDevice = (deleteFiles) => {
|
||||
const { platform, device_id, user } = this.props.item;
|
||||
seafileAPI.sysAdminUnlinkDevice(platform, device_id, user, deleteFiles).then((res) => {
|
||||
this.setState({unlinked: true});
|
||||
let message = gettext('Successfully unlinked the device.');
|
||||
toaster.success(message);
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
const { unlinked, isUnlinkDeviceDialogOpen, isOpIconShown } = this.state;
|
||||
|
||||
if (unlinked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td>{item.user_name}</td>
|
||||
<td>{item.platform}{' / '}{item.client_version}</td>
|
||||
<td>{item.device_name}</td>
|
||||
<td>{item.last_login_ip}</td>
|
||||
<td>
|
||||
<span title={moment(item.last_accessed).format('llll')}>{moment(item.last_accessed).fromNow()}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" className={`sf2-icon-delete action-icon ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Unlink')} onClick={this.handleUnlink}></a>
|
||||
</td>
|
||||
</tr>
|
||||
{isUnlinkDeviceDialogOpen &&
|
||||
<SysAdminUnlinkDevice
|
||||
unlinkDevice={this.unlinkDevice}
|
||||
toggleDialog={this.toggleUnlinkDeviceDialog}
|
||||
/>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DevicesByPlatform extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
devicesData: {},
|
||||
pageInfo: {},
|
||||
perPage: 50
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.getDevicesListByPage(1);
|
||||
}
|
||||
|
||||
getDevicesListByPage = (page) => {
|
||||
let platform = this.props.devicesPlatform;
|
||||
let per_page = this.state.perPage;
|
||||
seafileAPI.sysAdminListDevices(platform, page, per_page).then((res) => {
|
||||
this.setState({
|
||||
devicesData: res.data.devices,
|
||||
pageInfo: res.data.page_info,
|
||||
loading: false
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
getDevicesListByPage={this.getDevicesListByPage}
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.devicesData}
|
||||
pageInfo={this.state.pageInfo}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DevicesByPlatform;
|
175
frontend/src/pages/sys-admin/devices/devices-errors.js
Normal file
175
frontend/src/pages/sys-admin/devices/devices-errors.js
Normal file
@@ -0,0 +1,175 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
import { seafileAPI } from '../../../utils/seafile-api';
|
||||
import { siteRoot, loginUrl, gettext } from '../../../utils/constants';
|
||||
import toaster from '../../../components/toast';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import EmptyTip from '../../../components/empty-tip';
|
||||
import moment from 'moment';
|
||||
import Loading from '../../../components/loading';
|
||||
import { Link } from '@reach/router';
|
||||
import DevicesNav from './devices-nav';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No sync errors')}</h2>
|
||||
</EmptyTip>
|
||||
);
|
||||
const table = (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="16%">{gettext('User')}</th>
|
||||
<th width="20%">{gettext('Device')}{' / '}{gettext('Version')}</th>
|
||||
<th width="16%">{gettext('IP')}</th>
|
||||
<th width="16%">{gettext('Library')}</th>
|
||||
<th width="16%">{gettext('Error')}</th>
|
||||
<th width="16%">{gettext('Time')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return (<Item key={index} item={item} />);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShown: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({isOpIconShown: true});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({isOpIconShown: false});
|
||||
}
|
||||
|
||||
render() {
|
||||
let item = this.props.item;
|
||||
return (
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td><Link to={`${siteRoot}useradmin/info/${encodeURIComponent(item.email)}/`}>{item.name}</Link></td>
|
||||
<td>{item.device_name}{' / '}{item.client_version}</td>
|
||||
<td>{item.device_ip}</td>
|
||||
<td><Link to={`${siteRoot}sysadmin/#libs/${item.repo_id}`}>{item.repo_name}</Link></td>
|
||||
<td>{item.error_msg}</td>
|
||||
<td>
|
||||
<span className="item-meta-info" title={moment(item.last_accessed).format('llll')}>{moment(item.error_time).fromNow()}</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceErrors extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
devicesErrors: [],
|
||||
isCleanBtnShown: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
seafileAPI.sysAdminListDeviceErrors().then((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
devicesErrors: res.data,
|
||||
isCleanBtnShown: res.data.length > 0
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clean = () => {
|
||||
seafileAPI.sysAdminClearDeviceErrors().then((res) => {
|
||||
this.setState({
|
||||
devicesErrors: [],
|
||||
isCleanBtnShown: false
|
||||
});
|
||||
let message = gettext('Successfully cleaned all errors.');
|
||||
toaster.success(message);
|
||||
}).catch((error) => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
{this.state.isCleanBtnShown ? (
|
||||
<MainPanelTopbar>
|
||||
<Button className="operation-item" onClick={this.clean}>{gettext('Clean')}</Button>
|
||||
</MainPanelTopbar>
|
||||
) : (
|
||||
<MainPanelTopbar />
|
||||
)}
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<DevicesNav currentItem="errors" />
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.devicesErrors}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DeviceErrors;
|
41
frontend/src/pages/sys-admin/devices/devices-nav.js
Normal file
41
frontend/src/pages/sys-admin/devices/devices-nav.js
Normal file
@@ -0,0 +1,41 @@
|
||||
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: 'desktop', urlPart:'desktop-devices', text: gettext('Desktop')},
|
||||
{name: 'mobile', urlPart:'mobile-devices', text: gettext('Mobile')},
|
||||
{name: 'errors', urlPart:'device-errors', text: gettext('Errors')}
|
||||
];
|
||||
}
|
||||
|
||||
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}sys/${item.urlPart}/`} className={`nav-link${currentItem == item.name ? ' active' : ''}`}>{item.text}</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Nav.propTypes = propTypes;
|
||||
|
||||
export default Nav;
|
29
frontend/src/pages/sys-admin/devices/mobile-devices.js
Normal file
29
frontend/src/pages/sys-admin/devices/mobile-devices.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import DevicesNav from './devices-nav';
|
||||
import DevicesByPlatform from './devices-by-platform';
|
||||
import MainPanelTopbar from '../main-panel-topbar';
|
||||
|
||||
class MobileDevices extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar />
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<DevicesNav currentItem="mobile" />
|
||||
<DevicesByPlatform
|
||||
devicesPlatform={'mobile'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MobileDevices;
|
@@ -7,6 +7,9 @@ import MainPanel from './main-panel';
|
||||
import FileScanRecords from './file-scan-records';
|
||||
import WorkWeixinDepartments from './work-weixin-departments';
|
||||
import Info from './info';
|
||||
import DesktopDevices from './devices/desktop-devices';
|
||||
import MobileDevices from './devices/mobile-devices';
|
||||
import DeviceErrors from './devices/devices-errors';
|
||||
|
||||
import '../../assets/css/fa-solid.css';
|
||||
import '../../assets/css/fa-regular.css';
|
||||
@@ -25,7 +28,24 @@ class SysAdmin extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
let href = window.location.href.split('/');
|
||||
this.setState({currentTab: href[href.length - 2]});
|
||||
let currentTab = href[href.length - 2];
|
||||
let tmpTab;
|
||||
|
||||
const devicesUrlParts = ['desktop-devices', 'mobile-devices', 'device-errors'];
|
||||
const devicesTab = 'devices';
|
||||
tmpTab = this.getCurrentTabForPageList(devicesUrlParts, devicesTab);
|
||||
currentTab = tmpTab ? tmpTab : currentTab;
|
||||
|
||||
this.setState({currentTab: currentTab});
|
||||
}
|
||||
|
||||
getCurrentTabForPageList = (pageUrlPartList, curTab) => {
|
||||
const urlBase = `${siteRoot}sys/`;
|
||||
for (let i = 0, len = pageUrlPartList.length; i < len; i++) {
|
||||
if (location.href.indexOf(`${urlBase}${pageUrlPartList[i]}`) != -1) {
|
||||
return curTab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCloseSidePanel = () => {
|
||||
@@ -37,18 +57,17 @@ class SysAdmin extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { currentTab, isSidePanelClosed, } = this.state;
|
||||
let { currentTab, isSidePanelClosed } = this.state;
|
||||
|
||||
return (
|
||||
<div id="main">
|
||||
<SidePanel isSidePanelClosed={isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab}/>
|
||||
<MainPanel>
|
||||
<Router className="reach-router">
|
||||
<Info
|
||||
path={siteRoot + 'sys/info'}
|
||||
currentTab={currentTab}
|
||||
tabItemClick={this.tabItemClick}
|
||||
/>
|
||||
<Info path={siteRoot + 'sys/info'} />
|
||||
<DesktopDevices path={siteRoot + 'sys/desktop-devices'} />
|
||||
<MobileDevices path={siteRoot + 'sys/mobile-devices'} />
|
||||
<DeviceErrors path={siteRoot + 'sys/device-errors'} />
|
||||
<FileScanRecords
|
||||
path={siteRoot + 'sys/file-scan-records'}
|
||||
currentTab={currentTab}
|
||||
|
30
frontend/src/pages/sys-admin/main-panel-topbar.js
Normal file
30
frontend/src/pages/sys-admin/main-panel-topbar.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Account from '../../components/common/account';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.object,
|
||||
};
|
||||
|
||||
class MainPanelTopbar extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={`main-panel-north ${this.props.children ? 'border-left-show' : ''}`}>
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||
<div className="operation">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Account isAdminPanel={true} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MainPanelTopbar.propTypes = propTypes;
|
||||
|
||||
export default MainPanelTopbar;
|
@@ -48,10 +48,10 @@ class SidePanel extends React.Component {
|
||||
}
|
||||
{isDefaultAdmin &&
|
||||
<li className="nav-item">
|
||||
<a className='nav-link ellipsis' href={siteRoot + 'sysadmin/#desktop-devices/'}>
|
||||
<Link className={`nav-link ellipsis ${this.getActiveClass('devices')}`} to={siteRoot + 'sys/desktop-devices/'}>
|
||||
<span className="sf2-icon-monitor" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Devices')}</span>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
{constanceEnabled && canConfigSystem &&
|
||||
|
@@ -546,6 +546,12 @@ ul,ol,li {
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
.cur-view-path.tab-nav-container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
.cur-view-path.tab-nav-container .nav .nav-item .nav-link {
|
||||
margin: 0 0.75rem;
|
||||
}
|
||||
|
||||
/* side-panel */
|
||||
.side-panel {
|
||||
|
@@ -646,6 +646,9 @@ urlpatterns = [
|
||||
url(r'^useradmin/batchadduser/example/$', batch_add_user_example, name='batch_add_user_example'),
|
||||
|
||||
url(r'^sys/info/$', sysadmin_react_fake_view, name="sys_info"),
|
||||
url(r'^sys/desktop-devices/$', sysadmin_react_fake_view, name="sys_desktop_devices"),
|
||||
url(r'^sys/mobile-devices/$', sysadmin_react_fake_view, name="sys_mobile_devices"),
|
||||
url(r'^sys/device-errors/$', sysadmin_react_fake_view, name="sys_device_errors"),
|
||||
url(r'^sys/work-weixin/departments/$', sysadmin_react_fake_view, name="sys_work_weixin_departments"),
|
||||
|
||||
url(r'^client-login/$', client_token_login, name='client_token_login'),
|
||||
|
Reference in New Issue
Block a user