Added link to admin page (#2068)

* Added link to admin page

* Updated version

* Updated changelog
main
Boris Sekachev 6 years ago committed by GitHub
parent 1907b7924a
commit 18cdcd5daa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [Datumaro] Dataset statistics (<https://github.com/opencv/cvat/pull/1668>) - [Datumaro] Dataset statistics (<https://github.com/opencv/cvat/pull/1668>)
- Ability to change label color in tasks and predefined labels (<https://github.com/opencv/cvat/pull/2014>) - Ability to change label color in tasks and predefined labels (<https://github.com/opencv/cvat/pull/2014>)
- [Datumaro] Multi-dataset merge (https://github.com/opencv/cvat/pull/1695) - [Datumaro] Multi-dataset merge (https://github.com/opencv/cvat/pull/1695)
- Link to django admin page from UI (<https://github.com/opencv/cvat/pull/2068>)
### Changed ### Changed
- Shape coordinates are rounded to 2 digits in dumped annotations (<https://github.com/opencv/cvat/pull/1970>) - Shape coordinates are rounded to 2 digits in dumped annotations (<https://github.com/opencv/cvat/pull/1970>)

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.8.0", "version": "1.8.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.8.0", "version": "1.8.1",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {

@ -21,7 +21,7 @@ import ModelsPageContainer from 'containers/models-page/models-page';
import AnnotationPageContainer from 'containers/annotation-page/annotation-page'; import AnnotationPageContainer from 'containers/annotation-page/annotation-page';
import LoginPageContainer from 'containers/login-page/login-page'; import LoginPageContainer from 'containers/login-page/login-page';
import RegisterPageContainer from 'containers/register-page/register-page'; import RegisterPageContainer from 'containers/register-page/register-page';
import HeaderContainer from 'containers/header/header'; import Header from 'components/header/header';
import { customWaViewHit } from 'utils/enviroment'; import { customWaViewHit } from 'utils/enviroment';
import getCore from 'cvat-core-wrapper'; import getCore from 'cvat-core-wrapper';
@ -272,7 +272,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
return ( return (
<GlobalErrorBoundary> <GlobalErrorBoundary>
<Layout> <Layout>
<HeaderContainer> </HeaderContainer> <Header />
<Layout.Content style={{ height: '100%' }}> <Layout.Content style={{ height: '100%' }}>
<ShorcutsDialog /> <ShorcutsDialog />
<GlobalHotKeys keyMap={subKeyMap} handlers={handlers}> <GlobalHotKeys keyMap={subKeyMap} handlers={handlers}>

@ -3,9 +3,9 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import './styles.scss'; import './styles.scss';
import React, { MouseEvent } from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router'; import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'; import { useHistory } from 'react-router';
import { Row, Col } from 'antd/lib/grid'; import { Row, Col } from 'antd/lib/grid';
import Layout from 'antd/lib/layout'; import Layout from 'antd/lib/layout';
import Icon from 'antd/lib/icon'; import Icon from 'antd/lib/icon';
@ -15,50 +15,129 @@ import Dropdown from 'antd/lib/dropdown';
import Modal from 'antd/lib/modal'; import Modal from 'antd/lib/modal';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import { CVATLogo, AccountIcon } from 'icons'; import getCore from 'cvat-core-wrapper';
import consts from 'consts'; import consts from 'consts';
import { CVATLogo, AccountIcon } from 'icons';
import ChangePasswordDialog from 'components/change-password-modal/change-password-modal'; import ChangePasswordDialog from 'components/change-password-modal/change-password-modal';
import { switchSettingsDialog as switchSettingsDialogAction } from 'actions/settings-actions';
import { logoutAsync, authActions } from 'actions/auth-actions';
import { SupportedPlugins, CombinedState } from 'reducers/interfaces';
import SettingsModal from './settings-modal/settings-modal'; import SettingsModal from './settings-modal/settings-modal';
interface HeaderContainerProps { const core = getCore();
onLogout: () => void;
switchSettingsDialog: (show: boolean) => void; interface Tool {
switchChangePasswordDialog: (show: boolean) => void; name: string;
logoutFetching: boolean; description: string;
changePasswordFetching: boolean; server: {
installedAnalytics: boolean; host: string;
serverHost: string; version: string;
username: string; };
toolName: string; core: {
serverVersion: string; version: string;
serverDescription: string; };
coreVersion: string; canvas: {
canvasVersion: string; version: string;
uiVersion: string; };
ui: {
version: string;
};
}
interface StateToProps {
user: any;
tool: Tool;
switchSettingsShortcut: string; switchSettingsShortcut: string;
settingsDialogShown: boolean; settingsDialogShown: boolean;
changePasswordDialogShown: boolean; changePasswordDialogShown: boolean;
changePasswordFetching: boolean;
logoutFetching: boolean;
installedAnalytics: boolean;
renderChangePasswordItem: boolean; renderChangePasswordItem: boolean;
} }
type Props = HeaderContainerProps & RouteComponentProps; interface DispatchToProps {
onLogout: () => void;
switchSettingsDialog: (show: boolean) => void;
switchChangePasswordDialog: (show: boolean) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
auth: {
user,
fetching: logoutFetching,
fetching: changePasswordFetching,
showChangePasswordDialog: changePasswordDialogShown,
allowChangePassword: renderChangePasswordItem,
},
plugins: {
list,
},
about: {
server,
packageVersion,
},
shortcuts: {
normalizedKeyMap,
},
settings: {
showDialog: settingsDialogShown,
},
} = state;
return {
user,
tool: {
name: server.name as string,
description: server.description as string,
server: {
host: core.config.backendAPI.slice(0, -7),
version: server.version as string,
},
canvas: {
version: packageVersion.canvas,
},
core: {
version: packageVersion.core,
},
ui: {
version: packageVersion.ui,
},
},
switchSettingsShortcut: normalizedKeyMap.SWITCH_SETTINGS,
settingsDialogShown,
changePasswordDialogShown,
changePasswordFetching,
logoutFetching,
installedAnalytics: list[SupportedPlugins.ANALYTICS],
renderChangePasswordItem,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
onLogout: (): void => dispatch(logoutAsync()),
switchSettingsDialog: (show: boolean): void => dispatch(switchSettingsDialogAction(show)),
switchChangePasswordDialog: (show: boolean): void => (
dispatch(authActions.switchChangePasswordDialog(show))
),
};
}
type Props = StateToProps & DispatchToProps;
function HeaderContainer(props: Props): JSX.Element { function HeaderContainer(props: Props): JSX.Element {
const { const {
user,
tool,
installedAnalytics, installedAnalytics,
username,
toolName,
serverHost,
serverVersion,
serverDescription,
coreVersion,
canvasVersion,
uiVersion,
onLogout,
logoutFetching, logoutFetching,
changePasswordFetching, changePasswordFetching,
settingsDialogShown, settingsDialogShown,
switchSettingsShortcut, switchSettingsShortcut,
onLogout,
switchSettingsDialog, switchSettingsDialog,
switchChangePasswordDialog, switchChangePasswordDialog,
renderChangePasswordItem, renderChangePasswordItem,
@ -72,20 +151,22 @@ function HeaderContainer(props: Props): JSX.Element {
GITHUB_URL, GITHUB_URL,
} = consts; } = consts;
function aboutModal(): void { const history = useHistory();
function showAboutModal(): void {
Modal.info({ Modal.info({
title: `${toolName}`, title: `${tool.name}`,
content: ( content: (
<div> <div>
<p> <p>
{`${serverDescription}`} {`${tool.description}`}
</p> </p>
<p> <p>
<Text strong> <Text strong>
Server version: Server version:
</Text> </Text>
<Text type='secondary'> <Text type='secondary'>
{` ${serverVersion}`} {` ${tool.server.version}`}
</Text> </Text>
</p> </p>
<p> <p>
@ -93,7 +174,7 @@ function HeaderContainer(props: Props): JSX.Element {
Core version: Core version:
</Text> </Text>
<Text type='secondary'> <Text type='secondary'>
{` ${coreVersion}`} {` ${tool.core.version}`}
</Text> </Text>
</p> </p>
<p> <p>
@ -101,7 +182,7 @@ function HeaderContainer(props: Props): JSX.Element {
Canvas version: Canvas version:
</Text> </Text>
<Text type='secondary'> <Text type='secondary'>
{` ${canvasVersion}`} {` ${tool.canvas.version}`}
</Text> </Text>
</p> </p>
<p> <p>
@ -109,7 +190,7 @@ function HeaderContainer(props: Props): JSX.Element {
UI version: UI version:
</Text> </Text>
<Text type='secondary'> <Text type='secondary'>
{` ${uiVersion}`} {` ${tool.ui.version}`}
</Text> </Text>
</p> </p>
<Row type='flex' justify='space-around'> <Row type='flex' justify='space-around'>
@ -131,16 +212,27 @@ function HeaderContainer(props: Props): JSX.Element {
const menu = ( const menu = (
<Menu className='cvat-header-menu' mode='vertical'> <Menu className='cvat-header-menu' mode='vertical'>
{user.isStaff && (
<Menu.Item
onClick={(): void => {
// false positive
// eslint-disable-next-line
window.open(`${tool.server.host}/admin`, '_blank');
}}
>
<Icon type='control' />
Admin page
</Menu.Item>
)}
<Menu.Item <Menu.Item
title={`Press ${switchSettingsShortcut} to switch`} title={`Press ${switchSettingsShortcut} to switch`}
onClick={ onClick={() => switchSettingsDialog(true)}
(): void => switchSettingsDialog(true)
}
> >
<Icon type='setting' /> <Icon type='setting' />
Settings Settings
</Menu.Item> </Menu.Item>
<Menu.Item onClick={() => aboutModal()}> <Menu.Item onClick={showAboutModal}>
<Icon type='info-circle' /> <Icon type='info-circle' />
About About
</Menu.Item> </Menu.Item>
@ -178,7 +270,7 @@ function HeaderContainer(props: Props): JSX.Element {
onClick={ onClick={
(event: React.MouseEvent): void => { (event: React.MouseEvent): void => {
event.preventDefault(); event.preventDefault();
props.history.push('/tasks?page=1'); history.push('/tasks?page=1');
} }
} }
> >
@ -192,7 +284,7 @@ function HeaderContainer(props: Props): JSX.Element {
onClick={ onClick={
(event: React.MouseEvent): void => { (event: React.MouseEvent): void => {
event.preventDefault(); event.preventDefault();
props.history.push('/models'); history.push('/models');
} }
} }
> >
@ -203,13 +295,13 @@ function HeaderContainer(props: Props): JSX.Element {
<Button <Button
className='cvat-header-button' className='cvat-header-button'
type='link' type='link'
href={`${serverHost}/analytics/app/kibana`} href={`${tool.server.host}/analytics/app/kibana`}
onClick={ onClick={
(event: React.MouseEvent): void => { (event: React.MouseEvent): void => {
event.preventDefault(); event.preventDefault();
// false positive // false positive
// eslint-disable-next-line // eslint-disable-next-line
window.open(`${serverHost}/analytics/app/kibana`, '_blank'); window.open(`${tool.server.host}/analytics/app/kibana`, '_blank');
} }
} }
> >
@ -237,13 +329,13 @@ function HeaderContainer(props: Props): JSX.Element {
<Button <Button
className='cvat-header-button' className='cvat-header-button'
type='link' type='link'
href={`${serverHost}/documentation/user_guide.html`} href={`${tool.server.host}/documentation/user_guide.html`}
onClick={ onClick={
(event: React.MouseEvent): void => { (event: React.MouseEvent): void => {
event.preventDefault(); event.preventDefault();
// false positive // false positive
// eslint-disable-next-line // eslint-disable-next-line
window.open(`${serverHost}/documentation/user_guide.html`, '_blank') window.open(`${tool.server.host}/documentation/user_guide.html`, '_blank')
} }
} }
> >
@ -254,7 +346,7 @@ function HeaderContainer(props: Props): JSX.Element {
<span> <span>
<Icon className='cvat-header-account-icon' component={AccountIcon} /> <Icon className='cvat-header-account-icon' component={AccountIcon} />
<Text strong> <Text strong>
{username.length > 14 ? `${username.slice(0, 10)} ...` : username} {user.username.length > 14 ? `${user.username.slice(0, 10)} ...` : user.username}
</Text> </Text>
<Icon className='cvat-header-menu-icon' type='caret-down' /> <Icon className='cvat-header-menu-icon' type='caret-down' />
</span> </span>
@ -275,4 +367,20 @@ function HeaderContainer(props: Props): JSX.Element {
); );
} }
export default withRouter(HeaderContainer); function propsAreTheSame(prevProps: Props, nextProps: Props): boolean {
let equal = true;
for (const prop in nextProps) {
if (prop in prevProps && (prevProps as any)[prop] !== (nextProps as any)[prop]) {
if (prop !== 'tool') {
equal = false;
}
}
}
return equal;
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(React.memo(HeaderContainer, propsAreTheSame));

@ -138,7 +138,7 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
}], }],
})( })(
<Input <Input
prefix={<Icon type='user-add' style={{ color: 'rgba(0,0,0,.25)' }} />} prefix={<Icon type='user-add' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='First name' placeholder='First name'
/>, />,
)} )}
@ -159,7 +159,7 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
}], }],
})( })(
<Input <Input
prefix={<Icon type='user-add' style={{ color: 'rgba(0,0,0,.25)' }} />} prefix={<Icon type='user-add' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Last name' placeholder='Last name'
/>, />,
)} )}
@ -181,7 +181,7 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
}], }],
})( })(
<Input <Input
prefix={<Icon type='user-add' style={{ color: 'rgba(0,0,0,.25)' }} />} prefix={<Icon type='user-add' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Username' placeholder='Username'
/>, />,
)} )}
@ -205,7 +205,7 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
})( })(
<Input <Input
autoComplete='email' autoComplete='email'
prefix={<Icon type='mail' style={{ color: 'rgba(0,0,0,.25)' }} />} prefix={<Icon type='mail' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Email address' placeholder='Email address'
/>, />,
)} )}
@ -227,7 +227,7 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
}], }],
})(<Input.Password })(<Input.Password
autoComplete='new-password' autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0,0,0,.25)' }} />} prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Password' placeholder='Password'
/>)} />)}
</Form.Item> </Form.Item>
@ -248,7 +248,7 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
}], }],
})(<Input.Password })(<Input.Password
autoComplete='new-password' autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0,0,0,.25)' }} />} prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Confirm password' placeholder='Confirm password'
/>)} />)}
</Form.Item> </Form.Item>
@ -279,7 +279,7 @@ class RegisterFormComponent extends React.PureComponent<RegisterFormProps> {
target='_blank' target='_blank'
href={userAgreement.url} href={userAgreement.url}
> >
{userAgreement.displayText} {` ${userAgreement.displayText}`}
</a> </a>
</Checkbox>, </Checkbox>,
)} )}

@ -1,97 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import { connect } from 'react-redux';
import getCore from 'cvat-core-wrapper';
import HeaderComponent from 'components/header/header';
import { SupportedPlugins, CombinedState } from 'reducers/interfaces';
import { logoutAsync, authActions } from 'actions/auth-actions';
import { switchSettingsDialog } from 'actions/settings-actions';
const core = getCore();
interface StateToProps {
logoutFetching: boolean;
installedAnalytics: boolean;
username: string;
toolName: string;
serverHost: string;
serverVersion: string;
serverDescription: string;
coreVersion: string;
canvasVersion: string;
uiVersion: string;
switchSettingsShortcut: string;
settingsDialogShown: boolean;
changePasswordDialogShown: boolean;
changePasswordFetching: boolean;
renderChangePasswordItem: boolean;
}
interface DispatchToProps {
onLogout: () => void;
switchSettingsDialog: (show: boolean) => void;
switchChangePasswordDialog: (show: boolean) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
auth: {
fetching: logoutFetching,
fetching: changePasswordFetching,
user: {
username,
},
showChangePasswordDialog: changePasswordDialogShown,
allowChangePassword: renderChangePasswordItem,
},
plugins: {
list,
},
about: {
server,
packageVersion,
},
shortcuts: {
normalizedKeyMap,
},
settings: {
showDialog: settingsDialogShown,
},
} = state;
return {
logoutFetching,
installedAnalytics: list[SupportedPlugins.ANALYTICS],
username,
toolName: server.name as string,
serverHost: core.config.backendAPI.slice(0, -7),
serverDescription: server.description as string,
serverVersion: server.version as string,
coreVersion: packageVersion.core,
canvasVersion: packageVersion.canvas,
uiVersion: packageVersion.ui,
switchSettingsShortcut: normalizedKeyMap.SWITCH_SETTINGS,
settingsDialogShown,
changePasswordFetching,
changePasswordDialogShown,
renderChangePasswordItem,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
onLogout: (): void => dispatch(logoutAsync()),
switchSettingsDialog: (show: boolean): void => dispatch(switchSettingsDialog(show)),
switchChangePasswordDialog: (show: boolean): void => (
dispatch(authActions.switchChangePasswordDialog(show))
),
};
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(HeaderComponent);
Loading…
Cancel
Save