Added ability to change user password (#1954)

* added ability to change user password

* Update CHANGELOG.md

* fixed comments

* fixed linter warnings

* updated version of cvat-ui and cvat-core
main
Andrey Zhavoronkov 6 years ago committed by GitHub
parent 5da7d5ddfc
commit 4cb75a6a7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support creating multiple jobs for each task through python cli (https://github.com/opencv/cvat/pull/1950)
- python cli over https (<https://github.com/opencv/cvat/pull/1942>)
- Error message when plugins weren't able to initialize instead of infinite loading (<https://github.com/opencv/cvat/pull/1966>)
- Ability to change user password (<https://github.com/opencv/cvat/pull/1954>)
### Changed
- Smaller object details (<https://github.com/opencv/cvat/pull/1877>)

@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "3.3.1",
"version": "3.4.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js",
"scripts": {

@ -93,6 +93,10 @@
await serverProxy.server.logout();
};
cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => {
await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2);
};
cvat.server.authorized.implementation = async () => {
const result = await serverProxy.server.authorized();
return result;

@ -193,6 +193,19 @@ function build() {
.apiWrapper(cvat.server.logout);
return result;
},
/**
* Method allows to change user password
* @method changePassword
* @async
* @memberof module:API.cvat.server
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async changePassword(oldPassword, newPassword1, newPassword2) {
const result = await PluginRegistry
.apiWrapper(cvat.server.changePassword, oldPassword, newPassword1, newPassword2);
return result;
},
/**
* Method allows to know whether you are authorized on the server
* @method authorized

@ -246,6 +246,24 @@
Axios.defaults.headers.common.Authorization = '';
}
async function changePassword(oldPassword, newPassword1, newPassword2) {
try {
const data = JSON.stringify({
old_password: oldPassword,
new_password1: newPassword1,
new_password2:newPassword2,
});
await Axios.post(`${config.backendAPI}/auth/password/change`, data, {
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
throw generateError(errorData);
}
}
async function authorized() {
try {
await module.exports.users.getSelf();
@ -768,6 +786,7 @@
exception,
login,
logout,
changePassword,
authorized,
register,
request: serverRequest,

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

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

@ -5,6 +5,7 @@
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { UserConfirmation } from 'components/register-page/register-form';
import getCore from 'cvat-core-wrapper';
import isReachable from 'utils/url-checker';
const cvat = getCore();
@ -20,9 +21,16 @@ export enum AuthActionTypes {
LOGOUT = 'LOGOUT',
LOGOUT_SUCCESS = 'LOGOUT_SUCCESS',
LOGOUT_FAILED = 'LOGOUT_FAILED',
CHANGE_PASSWORD = 'CHANGE_PASSWORD',
CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS',
CHANGE_PASSWORD_FAILED = 'CHANGE_PASSWORD_FAILED',
SWITCH_CHANGE_PASSWORD_DIALOG = 'SWITCH_CHANGE_PASSWORD_DIALOG',
LOAD_AUTH_ACTIONS = 'LOAD_AUTH_ACTIONS',
LOAD_AUTH_ACTIONS_SUCCESS = 'LOAD_AUTH_ACTIONS_SUCCESS',
LOAD_AUTH_ACTIONS_FAILED = 'LOAD_AUTH_ACTIONS_FAILED',
}
const authActions = {
export const authActions = {
authorizeSuccess: (user: any) => createAction(AuthActionTypes.AUTHORIZED_SUCCESS, { user }),
authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }),
login: () => createAction(AuthActionTypes.LOGIN),
@ -34,6 +42,21 @@ const authActions = {
logout: () => createAction(AuthActionTypes.LOGOUT),
logoutSuccess: () => createAction(AuthActionTypes.LOGOUT_SUCCESS),
logoutFailed: (error: any) => createAction(AuthActionTypes.LOGOUT_FAILED, { error }),
changePassword: () => createAction(AuthActionTypes.CHANGE_PASSWORD),
changePasswordSuccess: () => createAction(AuthActionTypes.CHANGE_PASSWORD_SUCCESS),
changePasswordFailed: (error: any) => (
createAction(AuthActionTypes.CHANGE_PASSWORD_FAILED, { error })
),
switchChangePasswordDialog: (showChangePasswordDialog: boolean) => (
createAction(AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG, { showChangePasswordDialog })
),
loadServerAuthActions: () => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS),
loadServerAuthActionsSuccess: (allowChangePassword: boolean) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, { allowChangePassword })
),
loadServerAuthActionsFailed: (error: any) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error })
),
};
export type AuthActions = ActionUnion<typeof authActions>;
@ -100,3 +123,30 @@ export const authorizedAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.authorizeFailed(error));
}
};
export const changePasswordAsync = (oldPassword: string,
newPassword1: string, newPassword2: string): ThunkAction => async (dispatch) => {
dispatch(authActions.changePassword());
try {
await cvat.server.changePassword(oldPassword, newPassword1, newPassword2);
dispatch(authActions.changePasswordSuccess());
} catch (error) {
dispatch(authActions.changePasswordFailed(error));
}
};
export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.loadServerAuthActions());
try {
const promises: Promise<boolean>[] = [
isReachable(`${cvat.config.backendAPI}/auth/password/change`, 'OPTIONS'),
];
const [allowChangePassword] = await Promise.all(promises);
dispatch(authActions.loadServerAuthActionsSuccess(allowChangePassword));
} catch (error) {
dispatch(authActions.loadServerAuthActionsFailed(error));
}
};

@ -0,0 +1,166 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
import patterns from 'utils/validation-patterns';
export interface ChangePasswordData {
oldPassword: string;
newPassword1: string;
newPassword2: string;
}
type ChangePasswordFormProps = {
fetching: boolean;
onSubmit(loginData: ChangePasswordData): void;
} & FormComponentProps;
class ChangePasswordFormComponent extends React.PureComponent<ChangePasswordFormProps> {
private validateConfirmation = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (value && value !== form.getFieldValue('newPassword1')) {
callback('Two passwords that you enter is inconsistent!');
} else {
callback();
}
};
private validatePassword = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (!patterns.validatePasswordLength.pattern.test(value)) {
callback(patterns.validatePasswordLength.message);
}
if (!patterns.passwordContainsNumericCharacters.pattern.test(value)) {
callback(patterns.passwordContainsNumericCharacters.message);
}
if (!patterns.passwordContainsUpperCaseCharacter.pattern.test(value)) {
callback(patterns.passwordContainsUpperCaseCharacter.message);
}
if (!patterns.passwordContainsLowerCaseCharacter.pattern.test(value)) {
callback(patterns.passwordContainsLowerCaseCharacter.message);
}
if (value) {
form.validateFields(['newPassword2'], { force: true });
}
callback();
};
private handleSubmit = (e: React.FormEvent): void => {
e.preventDefault();
const {
form,
onSubmit,
} = this.props;
form.validateFields((error, values): void => {
if (!error) {
const validatedFields = {
...values,
confirmations: [],
};
onSubmit(validatedFields);
}
});
};
private renderOldPasswordField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('oldPassword', {
rules: [{
required: true,
message: 'Please input your current password!',
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Current password'
/>)}
</Form.Item>
);
}
private renderNewPasswordField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('newPassword1', {
rules: [{
required: true,
message: 'Please input new password!',
}, {
validator: this.validatePassword,
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='New password'
/>)}
</Form.Item>
);
}
private renderNewPasswordConfirmationField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('newPassword2', {
rules: [{
required: true,
message: 'Please confirm your new password!',
}, {
validator: this.validateConfirmation,
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Confirm new password'
/>)}
</Form.Item>
);
}
public render(): JSX.Element {
const { fetching } = this.props;
return (
<Form
onSubmit={this.handleSubmit}
className='change-password-form'
>
{this.renderOldPasswordField()}
{this.renderNewPasswordField()}
{this.renderNewPasswordConfirmationField()}
<Form.Item>
<Button
type='primary'
htmlType='submit'
className='change-password-form-button'
loading={fetching}
disabled={fetching}
>
Submit
</Button>
</Form.Item>
</Form>
);
}
}
export default Form.create<ChangePasswordFormProps>()(ChangePasswordFormComponent);

@ -0,0 +1,84 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import Modal from 'antd/lib/modal';
import Title from 'antd/lib/typography/Title';
import { changePasswordAsync } from 'actions/auth-actions';
import { CombinedState } from 'reducers/interfaces';
import ChangePasswordForm, { ChangePasswordData } from './change-password-form';
interface StateToProps {
fetching: boolean;
visible: boolean;
}
interface DispatchToProps {
onChangePassword(
oldPassword: string,
newPassword1: string,
newPassword2: string): void;
}
interface ChangePasswordPageComponentProps {
fetching: boolean;
visible: boolean;
onChangePassword: (oldPassword: string, newPassword1: string, newPassword2: string) => void;
onClose(): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
return {
fetching: state.auth.fetching,
visible: state.auth.showChangePasswordDialog,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return ({
onChangePassword(oldPassword: string, newPassword1: string, newPassword2: string): void {
dispatch(changePasswordAsync(oldPassword, newPassword1, newPassword2));
},
});
}
function ChangePasswordComponent(props: ChangePasswordPageComponentProps): JSX.Element {
const {
fetching,
onChangePassword,
visible,
onClose,
} = props;
return (
<Modal
title={<Title level={3}>Change password</Title>}
okType='primary'
okText='Submit'
footer={null}
visible={visible}
destroyOnClose
onCancel={onClose}
>
<ChangePasswordForm
onSubmit={(changePasswordData: ChangePasswordData): void => {
onChangePassword(
changePasswordData.oldPassword,
changePasswordData.newPassword1,
changePasswordData.newPassword2,
);
}}
fetching={fetching}
/>
</Modal>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ChangePasswordComponent);

@ -38,6 +38,7 @@ interface CVATAppProps {
resetMessages: () => void;
switchShortcutsDialog: () => void;
switchSettingsDialog: () => void;
loadAuthActions: () => void;
keyMap: Record<string, ExtendedKeyMapOptions>;
userInitialized: boolean;
userFetching: boolean;
@ -51,6 +52,9 @@ interface CVATAppProps {
aboutFetching: boolean;
userAgreementsFetching: boolean;
userAgreementsInitialized: boolean;
authActionsFetching: boolean;
authActionsInitialized: boolean;
allowChangePassword: boolean;
notifications: NotificationsState;
user: any;
}
@ -84,6 +88,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
loadAbout,
loadUserAgreements,
initPlugins,
loadAuthActions,
userInitialized,
userFetching,
formatsInitialized,
@ -97,6 +102,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
user,
userAgreementsFetching,
userAgreementsInitialized,
authActionsFetching,
authActionsInitialized,
} = this.props;
this.showErrors();
@ -116,6 +123,10 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
return;
}
if (!authActionsInitialized && !authActionsFetching) {
loadAuthActions();
}
if (!formatsInitialized && !formatsFetching) {
loadFormats();
}

@ -17,12 +17,15 @@ import Text from 'antd/lib/typography/Text';
import { CVATLogo, AccountIcon } from 'icons';
import consts from 'consts';
import ChangePasswordDialog from 'components/change-password-modal/change-password-modal';
import SettingsModal from './settings-modal/settings-modal';
interface HeaderContainerProps {
onLogout: () => void;
switchSettingsDialog: (show: boolean) => void;
switchChangePasswordDialog: (show: boolean) => void;
logoutFetching: boolean;
changePasswordFetching: boolean;
installedAnalytics: boolean;
serverHost: string;
username: string;
@ -34,6 +37,8 @@ interface HeaderContainerProps {
uiVersion: string;
switchSettingsShortcut: string;
settingsDialogShown: boolean;
changePasswordDialogShown: boolean;
renderChangePasswordItem: boolean;
}
type Props = HeaderContainerProps & RouteComponentProps;
@ -51,9 +56,12 @@ function HeaderContainer(props: Props): JSX.Element {
uiVersion,
onLogout,
logoutFetching,
changePasswordFetching,
settingsDialogShown,
switchSettingsShortcut,
switchSettingsDialog,
switchChangePasswordDialog,
renderChangePasswordItem,
} = props;
const {
@ -136,6 +144,16 @@ function HeaderContainer(props: Props): JSX.Element {
<Icon type='info-circle' />
About
</Menu.Item>
{renderChangePasswordItem && (
<Menu.Item
onClick={(): void => switchChangePasswordDialog(true)}
disabled={changePasswordFetching}
>
{changePasswordFetching ? <Icon type='loading' /> : <Icon type='edit' />}
Change password
</Menu.Item>
)}
<Menu.Item
onClick={onLogout}
disabled={logoutFetching}
@ -232,6 +250,13 @@ function HeaderContainer(props: Props): JSX.Element {
visible={settingsDialogShown}
onClose={() => switchSettingsDialog(false)}
/>
{ renderChangePasswordItem
&& (
<ChangePasswordDialog
onClose={() => switchChangePasswordDialog(false)}
/>
)}
</Layout.Header>
);
}

@ -7,7 +7,7 @@ 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 } from 'actions/auth-actions';
import { logoutAsync, authActions } from 'actions/auth-actions';
import { switchSettingsDialog } from 'actions/settings-actions';
const core = getCore();
@ -25,20 +25,27 @@ interface StateToProps {
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,
@ -68,6 +75,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
uiVersion: packageVersion.ui,
switchSettingsShortcut: normalizedKeyMap.SWITCH_SETTINGS,
settingsDialogShown,
changePasswordFetching,
changePasswordDialogShown,
renderChangePasswordItem,
};
}
@ -75,6 +85,9 @@ 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))
),
};
}

@ -14,7 +14,10 @@ import createRootReducer from 'reducers/root-reducer';
import createCVATStore, { getCVATStore } from 'cvat-store';
import logger, { LogType } from 'cvat-logger';
import { authorizedAsync } from 'actions/auth-actions';
import {
authorizedAsync,
loadAuthActionsAsync,
} from 'actions/auth-actions';
import { getFormatsAsync } from 'actions/formats-actions';
import { checkPluginsAsync } from 'actions/plugins-actions';
import { getUsersAsync } from 'actions/users-actions';
@ -27,6 +30,7 @@ import {
resetMessages,
} from './actions/notification-actions';
import {
CombinedState,
NotificationsState,
@ -48,6 +52,9 @@ interface StateToProps {
formatsFetching: boolean;
userAgreementsInitialized: boolean;
userAgreementsFetching: boolean;
authActionsFetching: boolean;
authActionsInitialized: boolean;
allowChangePassword: boolean;
notifications: NotificationsState;
user: any;
keyMap: Record<string, ExtendedKeyMapOptions>;
@ -64,6 +71,7 @@ interface DispatchToProps {
switchShortcutsDialog: () => void;
loadUserAgreements: () => void;
switchSettingsDialog: () => void;
loadAuthActions: () => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
@ -88,6 +96,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
formatsFetching: formats.fetching,
userAgreementsInitialized: userAgreements.initialized,
userAgreementsFetching: userAgreements.fetching,
authActionsFetching: auth.authActionsFetching,
authActionsInitialized: auth.authActionsInitialized,
allowChangePassword: auth.allowChangePassword,
notifications: state.notifications,
user: auth.user,
keyMap: shortcuts.keyMap,
@ -106,6 +117,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
resetMessages: (): void => dispatch(resetMessages()),
switchShortcutsDialog: (): void => dispatch(shortcutsActions.switchShortcutsDialog()),
switchSettingsDialog: (): void => dispatch(switchSettingsDialog()),
loadAuthActions: (): void => dispatch(loadAuthActionsAsync()),
};
}

@ -10,6 +10,10 @@ const defaultState: AuthState = {
initialized: false,
fetching: false,
user: null,
authActionsFetching: false,
authActionsInitialized: false,
allowChangePassword: false,
showChangePasswordDialog: false,
};
export default function (state = defaultState, action: AuthActions | boundariesActions): AuthState {
@ -69,6 +73,49 @@ export default function (state = defaultState, action: AuthActions | boundariesA
...state,
fetching: false,
};
case AuthActionTypes.CHANGE_PASSWORD:
return {
...state,
fetching: true,
};
case AuthActionTypes.CHANGE_PASSWORD_SUCCESS:
return {
...state,
fetching: false,
showChangePasswordDialog: false,
};
case AuthActionTypes.CHANGE_PASSWORD_FAILED:
return {
...state,
fetching: false,
};
case AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG:
return {
...state,
showChangePasswordDialog: typeof action.payload.showChangePasswordDialog === 'undefined'
? !state.showChangePasswordDialog
: action.payload.showChangePasswordDialog,
};
case AuthActionTypes.LOAD_AUTH_ACTIONS:
return {
...state,
authActionsFetching: true,
};
case AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS:
return {
...state,
authActionsFetching: false,
authActionsInitialized: true,
allowChangePassword: action.payload.allowChangePassword,
};
case AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED:
return {
...state,
authActionsFetching: false,
authActionsInitialized: true,
allowChangePassword: false,
};
case BoundariesActionTypes.RESET_AFTER_ERROR: {
return { ...defaultState };
}

@ -13,6 +13,10 @@ export interface AuthState {
initialized: boolean;
fetching: boolean;
user: any;
authActionsFetching: boolean;
authActionsInitialized: boolean;
showChangePasswordDialog: boolean;
allowChangePassword: boolean;
}
export interface TasksQuery {
@ -176,6 +180,8 @@ export interface NotificationsState {
login: null | ErrorState;
logout: null | ErrorState;
register: null | ErrorState;
changePassword: null | ErrorState;
loadAuthActions: null | ErrorState;
};
tasks: {
fetching: null | ErrorState;
@ -246,6 +252,9 @@ export interface NotificationsState {
models: {
inferenceDone: string;
};
auth: {
changePasswordDone: string;
};
};
}

@ -27,6 +27,8 @@ const defaultState: NotificationsState = {
login: null,
logout: null,
register: null,
changePassword: null,
loadAuthActions: null,
},
tasks: {
fetching: null,
@ -97,6 +99,9 @@ const defaultState: NotificationsState = {
models: {
inferenceDone: '',
},
auth: {
changePasswordDone: '',
},
},
};
@ -162,6 +167,48 @@ export default function (state = defaultState, action: AnyAction): Notifications
},
};
}
case AuthActionTypes.CHANGE_PASSWORD_SUCCESS: {
return {
...state,
messages: {
...state.messages,
auth: {
...state.messages.auth,
changePasswordDone: 'New password has been saved.',
},
},
};
}
case AuthActionTypes.CHANGE_PASSWORD_FAILED: {
return {
...state,
errors: {
...state.errors,
auth: {
...state.errors.auth,
changePassword: {
message: 'Could not change password',
reason: action.payload.error.toString(),
},
},
},
};
}
case AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED: {
return {
...state,
errors: {
...state.errors,
auth: {
...state.errors.auth,
loadAuthActions: {
message: 'Could not check available auth actions',
reason: action.payload.error.toString(),
},
},
},
};
}
case TasksActionTypes.EXPORT_DATASET_FAILED: {
const taskID = action.payload.task.id;
return {

@ -4,6 +4,7 @@
import getCore from 'cvat-core-wrapper';
import { SupportedPlugins } from 'reducers/interfaces';
import isReachable from './url-checker';
const core = getCore();
@ -11,16 +12,6 @@ const core = getCore();
class PluginChecker {
public static async check(plugin: SupportedPlugins): Promise<boolean> {
const serverHost = core.config.backendAPI.slice(0, -7);
const isReachable = async (url: string, method: string): Promise<boolean> => {
try {
await core.server.request(url, {
method,
});
return true;
} catch (error) {
return ![0, 404].includes(error.code);
}
};
switch (plugin) {
case SupportedPlugins.GIT_INTEGRATION: {

@ -0,0 +1,18 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import getCore from 'cvat-core-wrapper';
const core = getCore();
export default async (url: string, method: string): Promise<boolean> => {
try {
await core.server.request(url, {
method,
});
return true;
} catch (error) {
return ![0, 404].includes(error.code);
}
};

@ -216,6 +216,7 @@ AUTHENTICATION_BACKENDS = [
# https://github.com/pennersr/django-allauth
ACCOUNT_EMAIL_VERIFICATION = 'none'
OLD_PASSWORD_FIELD_ENABLED = True
# Django-RQ
# https://github.com/rq/django-rq

Loading…
Cancel
Save