diff --git a/cvat-ui/src/actions/about-actions.ts b/cvat-ui/src/actions/about-actions.ts new file mode 100644 index 00000000..ce76e59f --- /dev/null +++ b/cvat-ui/src/actions/about-actions.ts @@ -0,0 +1,55 @@ +import { AnyAction, Dispatch, ActionCreator } from 'redux'; +import { ThunkAction } from 'redux-thunk'; + +import getCore from 'cvat-core'; + +const core = getCore(); + +export enum AboutActionTypes { + GET_ABOUT = 'GET_ABOUT', + GET_ABOUT_SUCCESS = 'GET_ABOUT_SUCCESS', + GET_ABOUT_FAILED = 'GET_ABOUT_FAILED', +} + +function getAbout(): AnyAction { + const action = { + type: AboutActionTypes.GET_ABOUT, + payload: {}, + }; + + return action; +} + +function getAboutSuccess(about: any): AnyAction { + const action = { + type: AboutActionTypes.GET_ABOUT_SUCCESS, + payload: { about }, + }; + + return action; +} + +function getAboutFailed(error: any): AnyAction { + const action = { + type: AboutActionTypes.GET_ABOUT_FAILED, + payload: { error }, + }; + + return action; +} + +export function getAboutAsync(): +ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch(getAbout()); + + try { + const about = await core.server.about(); + dispatch( + getAboutSuccess(about), + ); + } catch (error) { + dispatch(getAboutFailed(error)); + } + }; +} diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 1c908c3d..61f1215c 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -29,6 +29,7 @@ import { NotificationsState } from 'reducers/interfaces'; type CVATAppProps = { loadFormats: () => void; loadUsers: () => void; + loadAbout: () => void; verifyAuthorized: () => void; initPlugins: () => void; resetErrors: () => void; @@ -40,6 +41,8 @@ type CVATAppProps = { formatsFetching: boolean; usersInitialized: boolean; usersFetching: boolean; + aboutInitialized: boolean; + aboutFetching: boolean; installedAutoAnnotation: boolean; installedTFAnnotation: boolean; installedTFSegmentation: boolean; @@ -57,12 +60,15 @@ export default class CVATApplication extends React.PureComponent { const { loadFormats, loadUsers, + loadAbout, initPlugins, userInitialized, formatsInitialized, formatsFetching, usersInitialized, usersFetching, + aboutInitialized, + aboutFetching, pluginsInitialized, pluginsFetching, user, @@ -84,6 +90,10 @@ export default class CVATApplication extends React.PureComponent { loadUsers(); } + if (!aboutInitialized && !aboutFetching) { + loadAbout(); + } + if (!pluginsInitialized && !pluginsFetching) { initPlugins(); } @@ -153,6 +163,7 @@ export default class CVATApplication extends React.PureComponent { const { tasks } = notifications.errors; const { formats } = notifications.errors; const { users } = notifications.errors; + const { about } = notifications.errors; const { share } = notifications.errors; const { models } = notifications.errors; const { annotation } = notifications.errors; @@ -160,7 +171,7 @@ export default class CVATApplication extends React.PureComponent { const shown = !!auth.authorized || !!auth.login || !!auth.logout || !!auth.register || !!tasks.fetching || !!tasks.updating || !!tasks.dumping || !!tasks.loading || !!tasks.exporting || !!tasks.deleting || !!tasks.creating || !!formats.fetching - || !!users.fetching || !!share.fetching || !!models.creating || !!models.starting + || !!users.fetching || !!about.fetching || !!share.fetching || !!models.creating || !!models.starting || !!models.fetching || !!models.deleting || !!models.inferenceStatusFetching || !!models.metaFetching || !!annotation.frameFetching || !!annotation.saving || !!annotation.jobFetching; @@ -204,6 +215,9 @@ export default class CVATApplication extends React.PureComponent { if (users.fetching) { showError(users.fetching.message, users.fetching.reason); } + if (about.fetching) { + showError(about.fetching.message, about.fetching.reason); + } if (share.fetching) { showError(share.fetching.message, share.fetching.reason); } @@ -248,6 +262,7 @@ export default class CVATApplication extends React.PureComponent { const { userInitialized, usersInitialized, + aboutInitialized, pluginsInitialized, formatsInitialized, installedAutoAnnotation, @@ -258,7 +273,7 @@ export default class CVATApplication extends React.PureComponent { const readyForRender = (userInitialized && user == null) || (userInitialized && formatsInitialized - && pluginsInitialized && usersInitialized); + && pluginsInitialized && usersInitialized && aboutInitialized); const withModels = installedAutoAnnotation || installedTFAnnotation || installedTFSegmentation; diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index 427ca21b..6a8eef8b 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -3,13 +3,15 @@ import React from 'react'; import { RouteComponentProps } from 'react-router'; import { withRouter } from 'react-router-dom'; - import { Layout, Icon, Button, Menu, Dropdown, + Modal, + Row, + Col, } from 'antd'; import Text from 'antd/lib/typography/Text'; @@ -31,6 +33,7 @@ interface HeaderContainerProps { installedTFAnnotation: boolean; installedTFSegmentation: boolean; username: string; + about: any; } type Props = HeaderContainerProps & RouteComponentProps; @@ -42,6 +45,7 @@ function HeaderContainer(props: Props): JSX.Element { installedTFAnnotation, installedAnalytics, username, + about, onLogout, logoutFetching, } = props; @@ -50,6 +54,47 @@ function HeaderContainer(props: Props): JSX.Element { || installedTFAnnotation || installedTFSegmentation; + function aboutModal() { + Modal.info({ + title: `${about.name}`, + content: ( +
+

+ {`${about.description}`} +

+

+ + Server version: + + + {` ${about.version}`} + +

+

+ + Client version: + + + {` ${core.client.version}`} + +

+ + What's new? + License + Need help? + Forum on Intel Developer Zone + +
+ ), + width : 800, + okButtonProps: { + style: { + width: '100px', + }, + }, + }) + } + const menu = ( Settings - + aboutModal()}> About diff --git a/cvat-ui/src/containers/header/header.tsx b/cvat-ui/src/containers/header/header.tsx index e0de3939..03f23b76 100644 --- a/cvat-ui/src/containers/header/header.tsx +++ b/cvat-ui/src/containers/header/header.tsx @@ -16,6 +16,7 @@ interface StateToProps { installedTFSegmentation: boolean; installedTFAnnotation: boolean; username: string; + about: any; } interface DispatchToProps { @@ -32,6 +33,7 @@ function mapStateToProps(state: CombinedState): StateToProps { installedTFSegmentation: plugins[SupportedPlugins.TF_SEGMENTATION], installedTFAnnotation: plugins[SupportedPlugins.TF_ANNOTATION], username: auth.user.username, + about: state.about.about, }; } diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx index 2d01144e..82fd5973 100644 --- a/cvat-ui/src/index.tsx +++ b/cvat-ui/src/index.tsx @@ -11,6 +11,7 @@ import { authorizedAsync } from './actions/auth-actions'; import { getFormatsAsync } from './actions/formats-actions'; import { checkPluginsAsync } from './actions/plugins-actions'; import { getUsersAsync } from './actions/users-actions'; +import { getAboutAsync } from './actions/about-actions'; import { resetErrors, resetMessages, @@ -30,6 +31,8 @@ interface StateToProps { userInitialized: boolean; usersInitialized: boolean; usersFetching: boolean; + aboutInitialized: boolean; + aboutFetching: boolean; formatsInitialized: boolean; formatsFetching: boolean; installedAutoAnnotation: boolean; @@ -43,6 +46,7 @@ interface DispatchToProps { loadFormats: () => void; verifyAuthorized: () => void; loadUsers: () => void; + loadAbout: () => void; initPlugins: () => void; resetErrors: () => void; resetMessages: () => void; @@ -53,6 +57,7 @@ function mapStateToProps(state: CombinedState): StateToProps { const { auth } = state; const { formats } = state; const { users } = state; + const { about } = state; return { userInitialized: auth.initialized, @@ -60,6 +65,8 @@ function mapStateToProps(state: CombinedState): StateToProps { pluginsFetching: plugins.fetching, usersInitialized: users.initialized, usersFetching: users.fetching, + aboutInitialized: about.initialized, + aboutFetching: about.fetching, formatsInitialized: formats.initialized, formatsFetching: formats.fetching, installedAutoAnnotation: plugins.plugins.AUTO_ANNOTATION, @@ -67,6 +74,7 @@ function mapStateToProps(state: CombinedState): StateToProps { installedTFAnnotation: plugins.plugins.TF_ANNOTATION, notifications: { ...state.notifications }, user: auth.user, + about: state.about, }; } @@ -76,6 +84,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { verifyAuthorized: (): void => dispatch(authorizedAsync()), initPlugins: (): void => dispatch(checkPluginsAsync()), loadUsers: (): void => dispatch(getUsersAsync()), + loadAbout: (): void => dispatch(getAboutAsync()), resetErrors: (): void => dispatch(resetErrors()), resetMessages: (): void => dispatch(resetMessages()), }; diff --git a/cvat-ui/src/reducers/about-reducer.ts b/cvat-ui/src/reducers/about-reducer.ts new file mode 100644 index 00000000..b9c3bd04 --- /dev/null +++ b/cvat-ui/src/reducers/about-reducer.ts @@ -0,0 +1,45 @@ +import { AnyAction } from 'redux'; +import { AboutState } from './interfaces'; + +import { AuthActionTypes } from '../actions/auth-actions'; +import { AboutActionTypes } from '../actions/about-actions'; + +const defaultState: AboutState = { + about: {}, + fetching: false, + initialized: false, +}; + +export default function (state: AboutState = defaultState, action: AnyAction): AboutState { + switch (action.type) { + case AboutActionTypes.GET_ABOUT: { + return { + ...state, + fetching: true, + initialized: false, + }; + } + case AboutActionTypes.GET_ABOUT_SUCCESS: + return { + ...state, + fetching: false, + initialized: true, + about: action.payload.about, + }; + case AboutActionTypes.GET_ABOUT_FAILED: + return { + ...state, + fetching: false, + initialized: true, + }; + case AuthActionTypes.LOGOUT_SUCCESS: { + return { + ...defaultState, + }; + } + default: + return { + ...state, + }; + } +} diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index dd9be592..f9dabfcc 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -94,6 +94,12 @@ export interface UsersState { initialized: boolean; } +export interface AboutState { + about: any; + fetching: boolean; + initialized: boolean; +} + export interface ShareFileInfo { // get this data from cvat-core name: string; type: 'DIR' | 'REG'; @@ -181,6 +187,9 @@ export interface NotificationsState { users: { fetching: null | ErrorState; }; + about: { + fetching: null | ErrorState; + }; share: { fetching: null | ErrorState; }; @@ -302,6 +311,7 @@ export interface CombinedState { auth: AuthState; tasks: TasksState; users: UsersState; + about: AboutState; share: ShareState; formats: FormatsState; plugins: PluginsState; diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index b81f0a07..fc95b3e4 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -6,6 +6,7 @@ import { ModelsActionTypes } from 'actions/models-actions'; import { ShareActionTypes } from 'actions/share-actions'; import { TasksActionTypes } from 'actions/tasks-actions'; import { UsersActionTypes } from 'actions/users-actions'; +import { AboutActionTypes } from '../actions/about-actions'; import { AnnotationActionTypes } from 'actions/annotation-actions'; import { NotificationsActionType } from 'actions/notification-actions'; @@ -34,6 +35,9 @@ const defaultState: NotificationsState = { users: { fetching: null, }, + about: { + fetching: null, + }, share: { fetching: null, }, @@ -282,6 +286,21 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } + case AboutActionTypes.GET_ABOUT_FAILED: { + return { + ...state, + errors: { + ...state.errors, + about: { + ...state.errors.about, + fetching: { + message: 'Could not get data from the server', + reason: action.payload.error.toString(), + }, + }, + }, + }; + } case ShareActionTypes.LOAD_SHARE_DATA_FAILED: { return { ...state, diff --git a/cvat-ui/src/reducers/root-reducer.ts b/cvat-ui/src/reducers/root-reducer.ts index de12c6b5..7a9c356b 100644 --- a/cvat-ui/src/reducers/root-reducer.ts +++ b/cvat-ui/src/reducers/root-reducer.ts @@ -2,6 +2,7 @@ import { combineReducers, Reducer } from 'redux'; import authReducer from './auth-reducer'; import tasksReducer from './tasks-reducer'; import usersReducer from './users-reducer'; +import aboutReducer from './about-reducer'; import shareReducer from './share-reducer'; import formatsReducer from './formats-reducer'; import pluginsReducer from './plugins-reducer'; @@ -15,6 +16,7 @@ export default function createRootReducer(): Reducer { auth: authReducer, tasks: tasksReducer, users: usersReducer, + about: aboutReducer, share: shareReducer, formats: formatsReducer, plugins: pluginsReducer,