Configurable REST for UI, minor improvements (#880)

main
Boris Sekachev 6 years ago committed by Nikita Manovich
parent 0bab60b7a0
commit 693e32e867

@ -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

@ -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

@ -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

@ -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<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
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<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch({
type: AuthActionTypes.LOGIN,
payload: {},
});
let users = null;
try {
await cvat.server.login(username, password);
@ -127,6 +140,11 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function logoutAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch({
type: AuthActionTypes.LOGOUT,
payload: {},
});
try {
await cvat.server.logout();
} catch (error) {

@ -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<Promise<void>, {}, {}, 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<Promise<void>, {}, {}, AnyAction> {
});
}
} catch (error) {
tasks.forEach((task: number): void => {
dispatch(getInferenceStatusFailed(task, error));
});
dispatch(fetchMetaFailed(error));
}
};
}

@ -100,7 +100,8 @@ export default class CVATApplication extends React.PureComponent<CVATAppProps> {
|| !!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<CVATAppProps> {
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);
}

@ -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) {
</span>
</span>
}>
<Menu.Item onClick={props.onLogout}>Logout</Menu.Item>
<Menu.Item
onClick={props.onLogout}
disabled={props.logoutFetching}
className='cvat-header-button'
>
{props.logoutFetching && <Icon type='loading'/>} Logout
</Menu.Item>
</Menu.SubMenu>
</Menu>
</div>

@ -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<LoginFormProps> {
{this.renderPasswordField()}
<Form.Item>
<Button type='primary' htmlType='submit' className='login-form-button'>
<Button
type='primary'
loading={this.props.fetching}
disabled={this.props.fetching}
htmlType='submit'
className='login-form-button'
>
Sign in
</Button>
</Form.Item>

@ -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
<Row type='flex' justify='center' align='middle'>
<Col {...sizes}>
<Title level={2}> Login </Title>
<LoginForm onSubmit={(loginData: LoginData) => {
<LoginForm fetching={props.fetching} onSubmit={(loginData: LoginData) => {
props.onLogin(loginData.username, loginData.password);
}}/>
<Row type='flex' justify='start' align='top'>

@ -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<RegisterFormProps> {
{this.renderPasswordConfirmationField()}
<Form.Item>
<Button type='primary' htmlType='submit' className='register-form-button'>
<Button
type='primary'
htmlType='submit'
className='register-form-button'
loading={this.props.fetching}
disabled={this.props.fetching}
>
Submit
</Button>
</Form.Item>

@ -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
<Row type='flex' justify='center' align='middle'>
<Col {...sizes}>
<Title level={2}> Create an account </Title>
<RegisterForm onSubmit={(registerData: RegisterData) => {
<RegisterForm fetching={props.fetching} onSubmit={(registerData: RegisterData) => {
props.onRegister(
registerData.username,
registerData.firstName,

@ -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 (
<HeaderComponent
logoutFetching={props.logoutFetching}
installedAnalytics={props.installedAnalytics}
installedTFAnnotation={props.installedTFAnnotation}
installedTFSegmentation={props.installedTFSegmentation}

@ -2,15 +2,20 @@ import React from 'react';
import { connect } from 'react-redux';
import { loginAsync } from '../../actions/auth-actions';
import LoginPageComponent from '../../components/login-page/login-page';
import { CombinedState } from '../../reducers/interfaces';
interface StateToProps {}
interface StateToProps {
fetching: boolean;
}
interface DispatchToProps {
login(username: string, password: string): void;
}
function mapStateToProps(): StateToProps {
return {};
function mapStateToProps(state: CombinedState): StateToProps {
return {
fetching: state.auth.fetching,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
@ -19,9 +24,10 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
};
}
function LoginPageContainer(props: DispatchToProps) {
function LoginPageContainer(props: DispatchToProps & StateToProps) {
return (
<LoginPageComponent
fetching={props.fetching}
onLogin={props.login}
/>
);

@ -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 (
<RegisterPageComponent
fetching={props.fetching}
onRegister={props.register}
/>
);

@ -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`;

@ -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;
}

@ -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;
}

@ -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;
};
};

@ -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,

@ -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,

@ -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 };
}

@ -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,

@ -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;
}

@ -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,

@ -59,7 +59,9 @@ module.exports = {
template: "./src/index.html",
inject: false,
}),
new Dotenv(),
new Dotenv({
systemvars: true,
}),
],
node: { fs: 'empty' },
};

@ -73,11 +73,11 @@ services:
https_proxy:
no_proxy:
socks_proxy:
dockerfile: Dockerfile.ui
environment:
REACT_APP_API_PROTOCOL: http
REACT_APP_API_HOST: localhost
REACT_APP_API_PORT: 8080
dockerfile: Dockerfile.ui
networks:
default:
aliases:

Loading…
Cancel
Save