diff --git a/Dockerfile.ui b/Dockerfile.ui index c5939296849..a1010edfc78 100644 --- a/Dockerfile.ui +++ b/Dockerfile.ui @@ -4,6 +4,9 @@ ARG http_proxy ARG https_proxy ARG no_proxy ARG socks_proxy +ARG REACT_APP_API_PORT +ARG REACT_APP_API_PROTOCOL +ARG REACT_APP_API_HOST ENV TERM=xterm \ http_proxy=${http_proxy} \ @@ -36,7 +39,7 @@ RUN npm install # Build source code COPY cvat-core/ /tmp/cvat-core/ COPY cvat-ui/ /tmp/cvat-ui/ -RUN mv .env.production .env && npm run build +RUN npm run build FROM nginx # Replace default.conf configuration to remove unnecessary rules diff --git a/cvat-ui/.env b/cvat-ui/.env deleted file mode 100644 index ac71a091100..00000000000 --- a/cvat-ui/.env +++ /dev/null @@ -1,9 +0,0 @@ -REACT_APP_VERSION=${npm_package_version} - -REACT_APP_API_PROTOCOL=http -REACT_APP_API_HOST=localhost -REACT_APP_API_PORT=7000 -REACT_APP_API_HOST_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT} -REACT_APP_API_FULL_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT}/api/v1 - -SKIP_PREFLIGHT_CHECK=true diff --git a/cvat-ui/.env.production b/cvat-ui/.env.production deleted file mode 100644 index d5af5e40249..00000000000 --- a/cvat-ui/.env.production +++ /dev/null @@ -1,9 +0,0 @@ -REACT_APP_VERSION=${npm_package_version} - -REACT_APP_API_PROTOCOL=http -REACT_APP_API_HOST=localhost -REACT_APP_API_PORT=8080 -REACT_APP_API_HOST_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT} -REACT_APP_API_FULL_URL=${REACT_APP_API_PROTOCOL}://${REACT_APP_API_HOST}:${REACT_APP_API_PORT}/api/v1 - -SKIP_PREFLIGHT_CHECK=true diff --git a/cvat-ui/src/actions/auth-actions.ts b/cvat-ui/src/actions/auth-actions.ts index 0d2a26fa7d4..a8b9748f03b 100644 --- a/cvat-ui/src/actions/auth-actions.ts +++ b/cvat-ui/src/actions/auth-actions.ts @@ -8,10 +8,13 @@ const cvat = getCore(); export enum AuthActionTypes { AUTHORIZED_SUCCESS = 'AUTHORIZED_SUCCESS', AUTHORIZED_FAILED = 'AUTHORIZED_FAILED', + LOGIN = 'LOGIN', LOGIN_SUCCESS = 'LOGIN_SUCCESS', LOGIN_FAILED = 'LOGIN_FAILED', + REGISTER = 'REGISTER', REGISTER_SUCCESS = 'REGISTER_SUCCESS', REGISTER_FAILED = 'REGISTER_FAILED', + LOGOUT = 'LOGOUT', LOGOUT_SUCCESS = 'LOGOUT_SUCCESS', LOGOUT_FAILED = 'LOGOUT_FAILED', } @@ -95,6 +98,11 @@ export function registerAsync( password2: string, ): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { + dispatch({ + type: AuthActionTypes.REGISTER, + payload: {}, + }); + let users = null; try { await cvat.server.register(username, firstName, lastName, @@ -112,6 +120,11 @@ export function registerAsync( export function loginAsync(username: string, password: string): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { + dispatch({ + type: AuthActionTypes.LOGIN, + payload: {}, + }); + let users = null; try { await cvat.server.login(username, password); @@ -127,6 +140,11 @@ ThunkAction, {}, {}, AnyAction> { export function logoutAsync(): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { + dispatch({ + type: AuthActionTypes.LOGOUT, + payload: {}, + }); + try { await cvat.server.logout(); } catch (error) { diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index c2745195f42..da51548b877 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -24,6 +24,7 @@ export enum ModelsActionTypes { INFER_MODEL = 'INFER_MODEL', INFER_MODEL_SUCCESS = 'INFER_MODEL_SUCCESS', INFER_MODEL_FAILED = 'INFER_MODEL_FAILED', + FETCH_META_FAILED = 'FETCH_META_FAILED', GET_INFERENCE_STATUS = 'GET_INFERENCE_STATUS', GET_INFERENCE_STATUS_SUCCESS = 'GET_INFERENCE_STATUS_SUCCESS', GET_INFERENCE_STATUS_FAILED = 'GET_INFERENCE_STATUS_FAILED', @@ -329,6 +330,16 @@ ThunkAction, {}, {}, AnyAction> { }; } +function fetchMetaFailed(error: any): AnyAction { + const action = { + type: ModelsActionTypes.FETCH_META_FAILED, + payload: { + error, + }, + }; + + return action; +} function getInferenceStatusSuccess( taskID: number, @@ -419,7 +430,9 @@ async function timeoutCallback( dispatch(getInferenceStatusSuccess(taskID, activeInference)); } catch (error) { - dispatch(getInferenceStatusFailed(taskID, error)); + dispatch(getInferenceStatusFailed(taskID, new Error( + `Server request for the task ${taskID} was failed` + ))); } } @@ -514,9 +527,7 @@ ThunkAction, {}, {}, AnyAction> { }); } } catch (error) { - tasks.forEach((task: number): void => { - dispatch(getInferenceStatusFailed(task, error)); - }); + dispatch(fetchMetaFailed(error)); } }; } diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index ba9c40d7c49..0639dd7101a 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -100,7 +100,8 @@ export default class CVATApplication extends React.PureComponent { || !!tasks.fetching || !!tasks.updating || !!tasks.dumping || !!tasks.loading || !!tasks.exporting || !!tasks.deleting || !!tasks.creating || !!formats.fetching || !!users.fetching || !!share.fetching || !!models.creating || !!models.starting - || !!models.fetching || !!models.deleting || !!models.inferenceStatusFetching; + || !!models.fetching || !!models.deleting || !!models.inferenceStatusFetching + || !!models.metaFetching; if (auth.authorized) { showError('Could not check authorization on the server', auth.authorized); @@ -156,6 +157,9 @@ export default class CVATApplication extends React.PureComponent { if (models.deleting) { showError('Could not delete model from the server', models.deleting); } + if (models.metaFetching) { + showError('Could not fetch models meta information from the server', models.metaFetching); + } if (models.inferenceStatusFetching) { showError('Could not fetch inference status from the server', models.inferenceStatusFetching); } diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index 0efeb86b117..451d93fd933 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -18,6 +18,7 @@ const core = getCore(); interface HeaderContainerProps { onLogout: () => void; + logoutFetching: boolean; installedAnalytics: boolean; installedAutoAnnotation: boolean; installedTFAnnotation: boolean; @@ -81,7 +82,13 @@ function HeaderContainer(props: Props) { }> - Logout + + {props.logoutFetching && } Logout + diff --git a/cvat-ui/src/components/login-page/login-form.tsx b/cvat-ui/src/components/login-page/login-form.tsx index 1a0733baba1..b3536797b94 100644 --- a/cvat-ui/src/components/login-page/login-form.tsx +++ b/cvat-ui/src/components/login-page/login-form.tsx @@ -13,6 +13,7 @@ export interface LoginData { } type LoginFormProps = { + fetching: boolean; onSubmit(loginData: LoginData): void; } & FormComponentProps; @@ -80,7 +81,13 @@ class LoginFormComponent extends React.PureComponent { {this.renderPasswordField()} - diff --git a/cvat-ui/src/components/login-page/login-page.tsx b/cvat-ui/src/components/login-page/login-page.tsx index fb2befa0b2b..90113b463bf 100644 --- a/cvat-ui/src/components/login-page/login-page.tsx +++ b/cvat-ui/src/components/login-page/login-page.tsx @@ -13,6 +13,7 @@ import { import LoginForm, { LoginData } from './login-form'; interface LoginPageComponentProps { + fetching: boolean; onLogin: (username: string, password: string) => void; } @@ -29,7 +30,7 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps Login - { + { props.onLogin(loginData.username, loginData.password); }}/> diff --git a/cvat-ui/src/components/register-page/register-form.tsx b/cvat-ui/src/components/register-page/register-form.tsx index 7e671564b4c..2122aec4b96 100644 --- a/cvat-ui/src/components/register-page/register-form.tsx +++ b/cvat-ui/src/components/register-page/register-form.tsx @@ -19,6 +19,7 @@ export interface RegisterData { import patterns from '../../utils/validation-patterns'; type RegisterFormProps = { + fetching: boolean; onSubmit(registerData: RegisterData): void; } & FormComponentProps; @@ -212,7 +213,13 @@ class RegisterFormComponent extends React.PureComponent { {this.renderPasswordConfirmationField()} - diff --git a/cvat-ui/src/components/register-page/register-page.tsx b/cvat-ui/src/components/register-page/register-page.tsx index d38c023478c..5fd4025e63a 100644 --- a/cvat-ui/src/components/register-page/register-page.tsx +++ b/cvat-ui/src/components/register-page/register-page.tsx @@ -8,12 +8,12 @@ import Text from 'antd/lib/typography/Text'; import { Col, Row, - Modal, } from 'antd'; import RegisterForm, { RegisterData } from '../../components/register-page/register-form'; interface RegisterPageComponentProps { + fetching: boolean; onRegister: (username: string, firstName: string, lastName: string, email: string, password1: string, password2: string) => void; @@ -32,7 +32,7 @@ function RegisterPageComponent(props: RegisterPageComponentProps & RouteComponen Create an account - { + { props.onRegister( registerData.username, registerData.firstName, diff --git a/cvat-ui/src/containers/header/header.tsx b/cvat-ui/src/containers/header/header.tsx index e8ba4c1e8b2..f3ec07fbe63 100644 --- a/cvat-ui/src/containers/header/header.tsx +++ b/cvat-ui/src/containers/header/header.tsx @@ -10,6 +10,7 @@ import { import HeaderComponent from '../../components/header/header'; interface StateToProps { + logoutFetching: boolean; installedAnalytics: boolean; installedAutoAnnotation: boolean; installedTFSegmentation: boolean; @@ -25,6 +26,7 @@ function mapStateToProps(state: CombinedState): StateToProps { const { auth } = state; const { plugins } = state.plugins; return { + logoutFetching: state.auth.fetching, installedAnalytics: plugins[SupportedPlugins.ANALYTICS], installedAutoAnnotation: plugins[SupportedPlugins.AUTO_ANNOTATION], installedTFSegmentation: plugins[SupportedPlugins.TF_SEGMENTATION], @@ -42,6 +44,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { function HeaderContainer(props: StateToProps & DispatchToProps) { return ( ); diff --git a/cvat-ui/src/containers/register-page/register-page.tsx b/cvat-ui/src/containers/register-page/register-page.tsx index b4e8a3054e3..f989ece22bc 100644 --- a/cvat-ui/src/containers/register-page/register-page.tsx +++ b/cvat-ui/src/containers/register-page/register-page.tsx @@ -2,8 +2,11 @@ import React from 'react'; import { connect } from 'react-redux'; import { registerAsync } from '../../actions/auth-actions'; import RegisterPageComponent from '../../components/register-page/register-page'; +import { CombinedState } from '../../reducers/interfaces'; -interface StateToProps {} +interface StateToProps { + fetching: boolean; +} interface DispatchToProps { register: (username: string, firstName: string, @@ -11,8 +14,10 @@ interface DispatchToProps { password1: string, password2: string) => void; } -function mapStateToProps(): StateToProps { - return {}; +function mapStateToProps(state: CombinedState): StateToProps { + return { + fetching: state.auth.fetching, + }; } function mapDispatchToProps(dispatch: any): DispatchToProps { @@ -24,6 +29,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { function RegisterPageContainer(props: StateToProps & DispatchToProps) { return ( ); diff --git a/cvat-ui/src/core.ts b/cvat-ui/src/core.ts index 29187c10539..b1d987493ab 100644 --- a/cvat-ui/src/core.ts +++ b/cvat-ui/src/core.ts @@ -2,9 +2,12 @@ import _cvat from '../../cvat-core/src/api'; const cvat: any = _cvat; -const protocol = process.env.REACT_APP_API_PROTOCOL; -const host = process.env.REACT_APP_API_HOST; -const port = process.env.REACT_APP_API_PORT; +const protocol = typeof (process.env.REACT_APP_API_PROTOCOL) === 'undefined' + ? 'http' : process.env.REACT_APP_API_PROTOCOL; +const host = typeof (process.env.REACT_APP_API_HOST) === 'undefined' + ? 'localhost' : process.env.REACT_APP_API_HOST; +const port = typeof (process.env.REACT_APP_API_PORT) === 'undefined' + ? '7000' : process.env.REACT_APP_API_PORT; cvat.config.backendAPI = `${protocol}://${host}:${port}/api/v1`; diff --git a/cvat-ui/src/reducers/auth-reducer.ts b/cvat-ui/src/reducers/auth-reducer.ts index b4cf915a167..023aad82cf9 100644 --- a/cvat-ui/src/reducers/auth-reducer.ts +++ b/cvat-ui/src/reducers/auth-reducer.ts @@ -5,6 +5,7 @@ import { AuthState } from './interfaces'; const defaultState: AuthState = { initialized: false, + fetching: false, user: null, }; @@ -16,21 +17,60 @@ export default (state = defaultState, action: AnyAction): AuthState => { initialized: true, user: action.payload.user, }; + case AuthActionTypes.AUTHORIZED_FAILED: + return { + ...state, + initialized: true, + }; + case AuthActionTypes.LOGIN: + return { + ...state, + fetching: true, + } case AuthActionTypes.LOGIN_SUCCESS: return { ...state, + fetching: false, user: action.payload.user, }; + case AuthActionTypes.LOGIN_FAILED: + return { + ...state, + fetching: false, + }; + case AuthActionTypes.LOGOUT: + return { + ...state, + fetching: true, + }; case AuthActionTypes.LOGOUT_SUCCESS: return { ...state, + fetching: false, user: null, }; + case AuthActionTypes.LOGIN_FAILED: + return { + ...state, + fetching: false, + }; + case AuthActionTypes.REGISTER: + return { + ...state, + fetching: true, + user: action.payload.user, + }; case AuthActionTypes.REGISTER_SUCCESS: return { ...state, + fetching: false, user: action.payload.user, }; + case AuthActionTypes.REGISTER_FAILED: + return { + ...state, + fetching: false, + }; default: return state; } diff --git a/cvat-ui/src/reducers/formats-reducer.ts b/cvat-ui/src/reducers/formats-reducer.ts index 8eedb90acb3..01fa6e43d71 100644 --- a/cvat-ui/src/reducers/formats-reducer.ts +++ b/cvat-ui/src/reducers/formats-reducer.ts @@ -1,5 +1,6 @@ import { AnyAction } from 'redux'; import { FormatsActionTypes } from '../actions/formats-actions'; +import { AuthActionTypes } from '../actions/auth-actions'; import { FormatsState } from './interfaces'; @@ -33,6 +34,11 @@ export default (state = defaultState, action: AnyAction): FormatsState => { initialized: true, fetching: false, }; + 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 4f107fee8f1..7a2e20fa6a6 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -1,5 +1,6 @@ export interface AuthState { initialized: boolean; + fetching: boolean; user: any; } @@ -174,8 +175,9 @@ export interface NotificationsState { models: { creating: any; starting: any; - fetching: any; deleting: any; + fetching: any; + metaFetching: any; inferenceStatusFetching: any; }; }; diff --git a/cvat-ui/src/reducers/models-reducer.ts b/cvat-ui/src/reducers/models-reducer.ts index 8372c01f964..8636b224ab5 100644 --- a/cvat-ui/src/reducers/models-reducer.ts +++ b/cvat-ui/src/reducers/models-reducer.ts @@ -1,6 +1,7 @@ import { AnyAction } from 'redux'; import { ModelsActionTypes } from '../actions/models-actions'; +import { AuthActionTypes } from '../actions/auth-actions'; import { ModelsState } from './interfaces'; const defaultState: ModelsState = { @@ -106,6 +107,11 @@ export default function (state = defaultState, action: AnyAction): ModelsState { inferences, }; } + case AuthActionTypes.LOGOUT_SUCCESS: { + return { + ...defaultState, + } + } default: { return { ...state, diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index c4291e71cee..30e9a11798b 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -39,8 +39,9 @@ const defaultState: NotificationsState = { models: { creating: null, starting: null, - fetching: null, deleting: null, + fetching: null, + metaFetching: null, inferenceStatusFetching: null, }, }, @@ -279,6 +280,18 @@ export default function (state = defaultState, action: AnyAction): Notifications ...state, }; } + case ModelsActionTypes.FETCH_META_FAILED: { + return { + ...state, + errors: { + ...state.errors, + models: { + ...state.errors.models, + metaFetching: action.payload.error, + }, + }, + }; + } case ModelsActionTypes.GET_INFERENCE_STATUS_FAILED: { return { ...state, @@ -331,6 +344,11 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } + case AuthActionTypes.LOGOUT_SUCCESS: { + return { + ...defaultState, + } + } default: { return { ...state, diff --git a/cvat-ui/src/reducers/plugins-reducer.ts b/cvat-ui/src/reducers/plugins-reducer.ts index 27913b9629e..29290e18d45 100644 --- a/cvat-ui/src/reducers/plugins-reducer.ts +++ b/cvat-ui/src/reducers/plugins-reducer.ts @@ -1,6 +1,7 @@ import { AnyAction } from 'redux'; import { PluginsActionTypes } from '../actions/plugins-actions'; +import { AuthActionTypes } from '../actions/auth-actions'; import { registerGitPlugin } from '../utils/git-utils'; import { PluginsState, @@ -41,6 +42,11 @@ export default function (state = defaultState, action: AnyAction): PluginsState plugins, }; } + case AuthActionTypes.LOGOUT_SUCCESS: { + return { + ...defaultState, + } + } default: return { ...state }; } diff --git a/cvat-ui/src/reducers/share-reducer.ts b/cvat-ui/src/reducers/share-reducer.ts index b85ce5622d0..66a989f1970 100644 --- a/cvat-ui/src/reducers/share-reducer.ts +++ b/cvat-ui/src/reducers/share-reducer.ts @@ -1,7 +1,12 @@ import { AnyAction } from 'redux'; import { ShareActionTypes } from '../actions/share-actions'; -import { ShareState, ShareFileInfo, ShareItem } from './interfaces'; +import { AuthActionTypes } from '../actions/auth-actions'; +import { + ShareState, + ShareFileInfo, + ShareItem, +} from './interfaces'; const defaultState: ShareState = { root: { @@ -38,6 +43,11 @@ export default function (state = defaultState, action: AnyAction): ShareState { ...state, }; } + case AuthActionTypes.LOGOUT_SUCCESS: { + return { + ...defaultState, + } + } default: return { ...state, diff --git a/cvat-ui/src/reducers/tasks-reducer.ts b/cvat-ui/src/reducers/tasks-reducer.ts index 4b1be064872..0dc48fa64f6 100644 --- a/cvat-ui/src/reducers/tasks-reducer.ts +++ b/cvat-ui/src/reducers/tasks-reducer.ts @@ -1,5 +1,6 @@ import { AnyAction } from 'redux'; import { TasksActionTypes } from '../actions/tasks-actions'; +import { AuthActionTypes } from '../actions/auth-actions'; import { TasksState, Task } from './interfaces'; @@ -404,6 +405,11 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState }), }; } + case AuthActionTypes.LOGOUT_SUCCESS: { + return { + ...defaultState, + } + } default: return state; } diff --git a/cvat-ui/src/reducers/users-reducer.ts b/cvat-ui/src/reducers/users-reducer.ts index f86755a19ea..b1f10d42742 100644 --- a/cvat-ui/src/reducers/users-reducer.ts +++ b/cvat-ui/src/reducers/users-reducer.ts @@ -1,15 +1,16 @@ import { AnyAction } from 'redux'; import { UsersState } from './interfaces'; +import { AuthActionTypes } from '../actions/auth-actions'; import { UsersActionTypes } from '../actions/users-actions'; -const initialState: UsersState = { +const defaultState: UsersState = { users: [], fetching: false, initialized: false, }; -export default function (state: UsersState = initialState, action: AnyAction): UsersState { +export default function (state: UsersState = defaultState, action: AnyAction): UsersState { switch (action.type) { case UsersActionTypes.GET_USERS: { return { @@ -31,6 +32,11 @@ export default function (state: UsersState = initialState, action: AnyAction): U fetching: false, initialized: true, }; + case AuthActionTypes.LOGOUT_SUCCESS: { + return { + ...defaultState, + } + } default: return { ...state, diff --git a/cvat-ui/webpack.config.js b/cvat-ui/webpack.config.js index ae5688976a1..6f9ae26296b 100644 --- a/cvat-ui/webpack.config.js +++ b/cvat-ui/webpack.config.js @@ -59,7 +59,9 @@ module.exports = { template: "./src/index.html", inject: false, }), - new Dotenv(), + new Dotenv({ + systemvars: true, + }), ], node: { fs: 'empty' }, }; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 82dfa352e5a..a3f7c56f0be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,11 +73,11 @@ services: https_proxy: no_proxy: socks_proxy: + REACT_APP_API_PROTOCOL: http + REACT_APP_API_HOST: localhost + REACT_APP_API_PORT: 8080 dockerfile: Dockerfile.ui - environment: - REACT_APP_API_PROTOCOL: http - REACT_APP_API_HOST: localhost - REACT_APP_API_PORT: 8080 + networks: default: aliases: