diff --git a/CHANGELOG.md b/CHANGELOG.md index ba874209df24..41591cf7ba82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support cloud storage status () - Support cloud storage preview () - cvat-core: support cloud storages () +- cvat-ui: support cloud storages () ### Changed diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index e50cdbc8f8cb..eb67c763264f 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -24,7 +24,6 @@ function build() { const { FrameData } = require('./frames'); const { CloudStorage } = require('./cloud-storage'); - const enums = require('./enums'); const { diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index cbe2aa7d342c..58e4c13142c0 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.23.1", + "version": "1.24.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index b9f1175e5853..0e28fee82bd4 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.23.1", + "version": "1.24.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/cloud-storage-actions.ts b/cvat-ui/src/actions/cloud-storage-actions.ts new file mode 100644 index 000000000000..439bab75b64c --- /dev/null +++ b/cvat-ui/src/actions/cloud-storage-actions.ts @@ -0,0 +1,194 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { Dispatch, ActionCreator } from 'redux'; +import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; +import getCore from 'cvat-core-wrapper'; +import { CloudStoragesQuery, CloudStorage } from 'reducers/interfaces'; + +const cvat = getCore(); + +export enum CloudStorageActionTypes { + UPDATE_CLOUD_STORAGES_GETTING_QUERY = 'UPDATE_CLOUD_STORAGES_GETTING_QUERY', + GET_CLOUD_STORAGES = 'GET_CLOUD_STORAGES', + GET_CLOUD_STORAGE_SUCCESS = 'GET_CLOUD_STORAGES_SUCCESS', + GET_CLOUD_STORAGE_FAILED = 'GET_CLOUD_STORAGES_FAILED', + GET_CLOUD_STORAGE_STATUS = 'GET_CLOUD_STORAGE_STATUS', + GET_CLOUD_STORAGE_STATUS_SUCCESS = 'GET_CLOUD_STORAGE_STATUS_SUCCESS', + GET_CLOUD_STORAGE_STATUS_FAILED = 'GET_CLOUD_STORAGE_STATUS_FAILED', + GET_CLOUD_STORAGE_PREVIEW_FAILED = 'GET_CLOUD_STORAGE_PREVIEW_FAILED', + CREATE_CLOUD_STORAGE = 'CREATE_CLOUD_STORAGE', + CREATE_CLOUD_STORAGE_SUCCESS = 'CREATE_CLOUD_STORAGE_SUCCESS', + CREATE_CLOUD_STORAGE_FAILED = 'CREATE_CLOUD_STORAGE_FAILED', + DELETE_CLOUD_STORAGE = 'DELETE_CLOUD_STORAGE', + DELETE_CLOUD_STORAGE_SUCCESS = 'DELETE_CLOUD_STORAGE_SUCCESS', + DELETE_CLOUD_STORAGE_FAILED = 'DELETE_CLOUD_STORAGE_FAILED', + UPDATE_CLOUD_STORAGE = 'UPDATE_CLOUD_STORAGE', + UPDATE_CLOUD_STORAGE_SUCCESS = 'UPDATE_CLOUD_STORAGE_SUCCESS', + UPDATE_CLOUD_STORAGE_FAILED = 'UPDATE_CLOUD_STORAGE_FAILED', + LOAD_CLOUD_STORAGE_CONTENT = 'LOAD_CLOUD_STORAGE_CONTENT', + LOAD_CLOUD_STORAGE_CONTENT_FAILED = 'LOAD_CLOUD_STORAGE_CONTENT_FAILED', + LOAD_CLOUD_STORAGE_CONTENT_SUCCESS = 'LOAD_CLOUD_STORAGE_CONTENT_SUCCESS', +} + +const cloudStoragesActions = { + updateCloudStoragesGettingQuery: (query: Partial) => + createAction(CloudStorageActionTypes.UPDATE_CLOUD_STORAGES_GETTING_QUERY, { query }), + getCloudStorages: () => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGES), + getCloudStoragesSuccess: ( + array: any[], + previews: string[], + statuses: string[], + count: number, + query: Partial, + ) => + createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_SUCCESS, { + array, + previews, + statuses, + count, + query, + }), + getCloudStoragesFailed: (error: any, query: Partial) => + createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_FAILED, { error, query }), + deleteCloudStorage: (cloudStorageID: number) => + createAction(CloudStorageActionTypes.DELETE_CLOUD_STORAGE, { cloudStorageID }), + deleteCloudStorageSuccess: (cloudStorageID: number) => + createAction(CloudStorageActionTypes.DELETE_CLOUD_STORAGE_SUCCESS, { cloudStorageID }), + deleteCloudStorageFailed: (error: any, cloudStorageID: number) => + createAction(CloudStorageActionTypes.DELETE_CLOUD_STORAGE_FAILED, { error, cloudStorageID }), + createCloudStorage: () => createAction(CloudStorageActionTypes.CREATE_CLOUD_STORAGE), + createCloudStorageSuccess: (cloudStorageID: number) => + createAction(CloudStorageActionTypes.CREATE_CLOUD_STORAGE_SUCCESS, { cloudStorageID }), + createCloudStorageFailed: (error: any) => + createAction(CloudStorageActionTypes.CREATE_CLOUD_STORAGE_FAILED, { error }), + updateCloudStorage: () => createAction(CloudStorageActionTypes.UPDATE_CLOUD_STORAGE, {}), + updateCloudStorageSuccess: (cloudStorage: CloudStorage) => + createAction(CloudStorageActionTypes.UPDATE_CLOUD_STORAGE_SUCCESS, { cloudStorage }), + updateCloudStorageFailed: (cloudStorage: CloudStorage, error: any) => + createAction(CloudStorageActionTypes.UPDATE_CLOUD_STORAGE_FAILED, { cloudStorage, error }), + loadCloudStorageContent: () => createAction(CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT), + loadCloudStorageContentSuccess: (cloudStorageID: number, content: any) => + createAction(CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT_SUCCESS, { cloudStorageID, content }), + loadCloudStorageContentFailed: (cloudStorageID: number, error: any) => + createAction(CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT_FAILED, { cloudStorageID, error }), + getCloudStorageStatus: () => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS), + getCloudStorageStatusSuccess: (cloudStorageID: number, status: string) => + createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_SUCCESS, { cloudStorageID, status }), + getCloudStorageStatusFailed: (cloudStorageID: number, error: any) => + createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_FAILED, { cloudStorageID, error }), + getCloudStoragePreiewFailed: (cloudStorageID: number, error: any) => + createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW_FAILED, { cloudStorageID, error }), +}; + +export type CloudStorageActions = ActionUnion; + +export function getCloudStoragesAsync(query: Partial): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + dispatch(cloudStoragesActions.getCloudStorages()); + dispatch(cloudStoragesActions.updateCloudStoragesGettingQuery(query)); + + const filteredQuery = { ...query }; + for (const key in filteredQuery) { + if (filteredQuery[key] === null) { + delete filteredQuery[key]; + } + } + + let result = null; + try { + result = await cvat.cloudStorages.get(filteredQuery); + } catch (error) { + dispatch(cloudStoragesActions.getCloudStoragesFailed(error, query)); + return; + } + + const array = Array.from(result); + const promises = array.map((cloudStorage: CloudStorage): string => + (cloudStorage as any).getPreview().catch((error: any) => { + dispatch(cloudStoragesActions.getCloudStoragePreiewFailed(cloudStorage.id, error)); + return ''; + })); + + const statusPromises = array.map((cloudStorage: CloudStorage): string => + (cloudStorage as any).getStatus().catch((error: any) => { + dispatch(cloudStoragesActions.getCloudStorageStatusFailed(cloudStorage.id, error)); + return ''; + })); + + dispatch(cloudStoragesActions.getCloudStoragesSuccess( + array, + await Promise.all(promises), + await Promise.all(statusPromises), + result.count, + query, + )); + }; +} + +export function deleteCloudStorageAsync(cloudStorageInstance: any): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + try { + dispatch(cloudStoragesActions.deleteCloudStorage(cloudStorageInstance.id)); + await cloudStorageInstance.delete(); + } catch (error) { + dispatch(cloudStoragesActions.deleteCloudStorageFailed(error, cloudStorageInstance.id)); + return; + } + + dispatch(cloudStoragesActions.deleteCloudStorageSuccess(cloudStorageInstance.id)); + }; +} + +export function createCloudStorageAsync(data: any): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + const cloudStorageInstance = new cvat.classes.CloudStorage(data); + + dispatch(cloudStoragesActions.createCloudStorage()); + try { + const savedCloudStorage = await cloudStorageInstance.save(); + dispatch(cloudStoragesActions.createCloudStorageSuccess(savedCloudStorage.id)); + } catch (error) { + dispatch(cloudStoragesActions.createCloudStorageFailed(error)); + } + }; +} + +export function updateCloudStorageAsync(data: any): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + const cloudStorageInstance = new cvat.classes.CloudStorage(data); + + dispatch(cloudStoragesActions.updateCloudStorage()); + try { + const savedCloudStorage = await cloudStorageInstance.save(); + dispatch(cloudStoragesActions.updateCloudStorageSuccess(savedCloudStorage)); + } catch (error) { + dispatch(cloudStoragesActions.updateCloudStorageFailed(data, error)); + } + }; +} + +export function loadCloudStorageContentAsync(cloudStorage: CloudStorage): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + dispatch(cloudStoragesActions.loadCloudStorageContent()); + try { + const result = await cloudStorage.getContent(); + dispatch(cloudStoragesActions.loadCloudStorageContentSuccess(cloudStorage.id, result)); + } catch (error) { + dispatch(cloudStoragesActions.loadCloudStorageContentFailed(cloudStorage.id, error)); + } + }; +} + +// export function getCloudStorageStatusAsync(cloudStorage: CloudStorage): ThunkAction { +// return async (dispatch: ActionCreator): Promise => { +// dispatch(cloudStoragesActions.getCloudStorageStatus()); +// try { +// const result = await cloudStorage.getStatus(); +// dispatch(cloudStoragesActions.getCloudStorageStatusSuccess(cloudStorage.id, result)); +// } catch (error) { +// dispatch(cloudStoragesActions.getCloudStorageStatusFailed(cloudStorage.id, error)); +// } +// }; +// } diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index 468bfe8c3519..9d428a43eeb6 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -386,10 +386,13 @@ export function createTaskAsync(data: any): ThunkAction, {}, {}, A if (data.subset) { description.subset = data.subset; } + if (data.cloudStorageId) { + description.cloud_storage_id = data.cloudStorageId; + } const taskInstance = new cvat.classes.Task(description); taskInstance.clientFiles = data.files.local; - taskInstance.serverFiles = data.files.share; + taskInstance.serverFiles = data.files.share.concat(data.files.cloudStorage); taskInstance.remoteFiles = data.files.remote; if (data.advanced.repository) { diff --git a/cvat-ui/src/assets/S3.svg b/cvat-ui/src/assets/S3.svg new file mode 100644 index 000000000000..1340396745a3 --- /dev/null +++ b/cvat-ui/src/assets/S3.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/cvat-ui/src/assets/vscode-icons_file-type-azure.svg b/cvat-ui/src/assets/vscode-icons_file-type-azure.svg new file mode 100644 index 000000000000..e5356cee02e4 --- /dev/null +++ b/cvat-ui/src/assets/vscode-icons_file-type-azure.svg @@ -0,0 +1,4 @@ + + + + diff --git a/cvat-ui/src/base.scss b/cvat-ui/src/base.scss index 4097dbe04692..1cb927ff1835 100644 --- a/cvat-ui/src/base.scss +++ b/cvat-ui/src/base.scss @@ -24,6 +24,8 @@ $border-color-3: #242424; $border-color-hover: #40a9ff; $background-color-1: white; $background-color-2: #f1f1f1; +$notification-background-color-1: #d9ecff; +$notification-border-color-1: #1890ff; $transparent-color: rgba(0, 0, 0, 0); $player-slider-color: #979797; $player-buttons-color: #242424; diff --git a/cvat-ui/src/components/cloud-storages-page/cloud-storage-item.tsx b/cvat-ui/src/components/cloud-storages-page/cloud-storage-item.tsx new file mode 100644 index 000000000000..3c14bcae0dbc --- /dev/null +++ b/cvat-ui/src/components/cloud-storages-page/cloud-storage-item.tsx @@ -0,0 +1,145 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router'; +import { CloudSyncOutlined, MoreOutlined, QuestionCircleOutlined } from '@ant-design/icons'; +import Card from 'antd/lib/card'; +import Meta from 'antd/lib/card/Meta'; +import Paragraph from 'antd/lib/typography/Paragraph'; +import Text from 'antd/lib/typography/Text'; +import Button from 'antd/lib/button'; +import Dropdown from 'antd/lib/dropdown'; +import Menu from 'antd/lib/menu'; +import Modal from 'antd/lib/modal'; +import moment from 'moment'; + +import { CloudStorage, CombinedState } from 'reducers/interfaces'; +import { deleteCloudStorageAsync } from 'actions/cloud-storage-actions'; +import CVATTooltip from 'components/common/cvat-tooltip'; +import Status from './cloud-storage-status'; + +interface Props { + cloudStorageInstance: CloudStorage; +} + +export default function CloudStorageItemComponent(props: Props): JSX.Element { + const history = useHistory(); + const dispatch = useDispatch(); + + // cloudStorageInstance: {storage, preview, status} + const { cloudStorageInstance } = props; + const { + id, + displayName, + providerType, + owner, + createdDate, + updatedDate, + description, + } = cloudStorageInstance.storage; + const { preview, status } = cloudStorageInstance; + const deletes = useSelector((state: CombinedState) => state.cloudStorages.activities.deletes); + const deleted = cloudStorageInstance.storage.id in deletes ? deletes[cloudStorageInstance.storage.id] : false; + + const style: React.CSSProperties = {}; + + if (deleted) { + style.pointerEvents = 'none'; + style.opacity = 0.5; + } + + const onUpdate = useCallback(() => { + history.push(`/cloudstorages/update/${id}`); + }, []); + + const onDelete = useCallback(() => { + Modal.confirm({ + title: 'Please, confirm your action', + content: `You are going to remove the cloudstorage "${displayName}". Continue?`, + className: 'cvat-delete-cloud-storage-modal', + onOk: () => { + dispatch(deleteCloudStorageAsync(cloudStorageInstance.storage)); + }, + okButtonProps: { + type: 'primary', + danger: true, + }, + okText: 'Delete', + }); + }, [cloudStorageInstance.storage.id]); + + return ( + + {preview ? ( + Preview image + ) : ( +
+ +
+ )} + {description ? ( + + + + ) : null} + + )} + size='small' + style={style} + className='cvat-cloud-storage-item' + > + + {`#${id}: `} + {displayName} + + )} + description={( + <> + + Provider: + {providerType} + + + Created + {owner ? {`by ${owner.username}`} : null} + on + {moment(createdDate).format('MMMM Do YYYY')} + + + Last updated + {moment(updatedDate).fromNow()} + + + + Update + Delete + + )} + > + + + + ); +} diff --git a/cvat-ui/src/components/create-cloud-storage-page/cloud-storage-form.tsx b/cvat-ui/src/components/create-cloud-storage-page/cloud-storage-form.tsx new file mode 100644 index 000000000000..83e46ba8a572 --- /dev/null +++ b/cvat-ui/src/components/create-cloud-storage-page/cloud-storage-form.tsx @@ -0,0 +1,515 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React, { + useState, useEffect, useRef, +} from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router'; +import { Row, Col } from 'antd/lib/grid'; +import Button from 'antd/lib/button'; +import Form from 'antd/lib/form'; +import Select from 'antd/lib/select'; +import Input from 'antd/lib/input'; +import TextArea from 'antd/lib/input/TextArea'; +import notification from 'antd/lib/notification'; + +import { CombinedState, CloudStorage } from 'reducers/interfaces'; +import { createCloudStorageAsync, updateCloudStorageAsync } from 'actions/cloud-storage-actions'; +import { ProviderType, CredentialsType } from 'utils/enums'; +import { AzureProvider, S3Provider } from '../../icons'; +import S3Region from './s3-region'; +import ManifestsManager from './manifests-manager'; + +export interface Props { + cloudStorage?: CloudStorage; +} + +type CredentialsFormNames = 'key' | 'secret_key' | 'account_name' | 'session_token'; +type CredentialsCamelCaseNames = 'key' | 'secretKey' | 'accountName' | 'sessionToken'; + +interface CloudStorageForm { + credentials_type: CredentialsType; + display_name: string; + provider_type: ProviderType; + resource: string; + account_name?: string; + session_token?: string; + key?: string; + secret_key?: string; + SAS_token?: string; + description?: string; + region?: string; + manifests: string[]; +} + +export default function CreateCloudStorageForm(props: Props): JSX.Element { + const { cloudStorage } = props; + const dispatch = useDispatch(); + const history = useHistory(); + const [form] = Form.useForm(); + const shouldShowCreationNotification = useRef(false); + const shouldShowUpdationNotification = useRef(false); + const [providerType, setProviderType] = useState(null); + const [credentialsType, setCredentialsType] = useState(null); + const [selectedRegion, setSelectedRegion] = useState(undefined); + const newCloudStorageId = useSelector((state: CombinedState) => state.cloudStorages.activities.creates.id); + const attaching = useSelector((state: CombinedState) => state.cloudStorages.activities.creates.attaching); + const updating = useSelector((state: CombinedState) => state.cloudStorages.activities.updates.updating); + const updatedCloudStorageId = useSelector( + (state: CombinedState) => state.cloudStorages.activities.updates.cloudStorageID, + ); + const loading = cloudStorage ? updating : attaching; + const fakeCredentialsData = { + accountName: 'X'.repeat(24), + sessionToken: 'X'.repeat(300), + key: 'X'.repeat(20), + secretKey: 'X'.repeat(40), + }; + + const [keyVisibility, setKeyVisibility] = useState(false); + const [secretKeyVisibility, setSecretKeyVisibility] = useState(false); + const [sessionTokenVisibility, setSessionTokenVisibility] = useState(false); + const [accountNameVisibility, setAccountNameVisibility] = useState(false); + + const [manifestNames, setManifestNames] = useState([]); + + function initializeFields(): void { + setManifestNames(cloudStorage.manifests); + const fieldsValue: CloudStorageForm = { + credentials_type: cloudStorage.credentialsType, + display_name: cloudStorage.displayName, + description: cloudStorage.description, + provider_type: cloudStorage.providerType, + resource: cloudStorage.resourceName, + manifests: manifestNames, + }; + + setProviderType(cloudStorage.providerType); + setCredentialsType(cloudStorage.credentialsType); + + if (cloudStorage.credentialsType === CredentialsType.ACCOUNT_NAME_TOKEN_PAIR) { + fieldsValue.account_name = fakeCredentialsData.accountName; + fieldsValue.SAS_token = fakeCredentialsData.sessionToken; + } else if (cloudStorage.credentialsType === CredentialsType.KEY_SECRET_KEY_PAIR) { + fieldsValue.key = fakeCredentialsData.key; + fieldsValue.secret_key = fakeCredentialsData.secretKey; + } + + if (cloudStorage.providerType === ProviderType.AWS_S3_BUCKET && cloudStorage.specificAttributes) { + const region = new URLSearchParams(cloudStorage.specificAttributes).get('region'); + if (region) { + setSelectedRegion(region); + } + } + + form.setFieldsValue(fieldsValue); + } + + function onReset(): void { + if (cloudStorage) { + initializeFields(); + } else { + setManifestNames([]); + setSelectedRegion(undefined); + form.resetFields(); + } + } + + const onCancel = (): void => { + if (history.length) { + history.goBack(); + } else { + history.push('/cloudstorages'); + } + }; + + useEffect(() => { + onReset(); + }, []); + + useEffect(() => { + if ( + Number.isInteger(newCloudStorageId) && + shouldShowCreationNotification && + shouldShowCreationNotification.current + ) { + // Clear form + onReset(); + + notification.info({ + message: 'The cloud storage has been attached', + className: 'cvat-notification-create-cloud-storage-success', + }); + } + if (shouldShowCreationNotification !== undefined) { + shouldShowCreationNotification.current = true; + } + }, [newCloudStorageId]); + + useEffect(() => { + if (updatedCloudStorageId && shouldShowUpdationNotification && shouldShowUpdationNotification.current) { + notification.info({ + message: 'The cloud storage has been updated', + className: 'cvat-notification-update-cloud-storage-success', + }); + } + if (shouldShowUpdationNotification !== undefined) { + shouldShowUpdationNotification.current = true; + } + }, [updatedCloudStorageId]); + + useEffect(() => { + if (cloudStorage && cloudStorage.credentialsType !== CredentialsType.ANONYMOUS_ACCESS) { + notification.info({ + message: `For security reasons, your credentials are hidden and represented by fake values + that will not be taken into account when updating the cloud storage. + If you want to replace the original credentials, simply enter new ones.`, + className: 'cvat-notification-update-info-cloud-storage', + duration: 15, + }); + } + }, [cloudStorage]); + + const onSubmit = async (): Promise => { + let cloudStorageData: Record = {}; + const formValues = await form.validateFields(); + cloudStorageData = { ...formValues }; + if (formValues.region !== undefined) { + delete cloudStorageData.region; + cloudStorageData.specific_attributes = `region=${selectedRegion}`; + } + + if (cloudStorageData.credentials_type === CredentialsType.ACCOUNT_NAME_TOKEN_PAIR) { + delete cloudStorageData.SAS_token; + cloudStorageData.session_token = formValues.SAS_token; + } + + if (cloudStorageData.manifests && cloudStorageData.manifests.length) { + delete cloudStorageData.manifests; + cloudStorageData.manifests = form + .getFieldValue('manifests') + .map((manifest: any): string => manifest.name); + } + + if (cloudStorage) { + cloudStorageData.id = cloudStorage.id; + if (cloudStorageData.account_name === fakeCredentialsData.accountName) { + delete cloudStorageData.account_name; + } + if (cloudStorageData.key === fakeCredentialsData.key) { + delete cloudStorageData.key; + } + if (cloudStorageData.secret_key === fakeCredentialsData.secretKey) { + delete cloudStorageData.secret_key; + } + if (cloudStorageData.session_token === fakeCredentialsData.sessionToken) { + delete cloudStorageData.session_token; + } + dispatch(updateCloudStorageAsync(cloudStorageData)); + } else { + dispatch(createCloudStorageAsync(cloudStorageData)); + } + }; + + const resetCredentialsValues = (): void => { + form.setFieldsValue({ + key: undefined, + secret_key: undefined, + session_token: undefined, + account_name: undefined, + }); + }; + + const onFocusCredentialsItem = (credential: CredentialsCamelCaseNames, key: CredentialsFormNames): void => { + // reset fake credential when updating a cloud storage and cursor is in this field + if (cloudStorage && form.getFieldValue(key) === fakeCredentialsData[credential]) { + form.setFieldsValue({ + [key]: undefined, + }); + } + }; + + const onBlurCredentialsItem = ( + credential: CredentialsCamelCaseNames, + key: CredentialsFormNames, + setVisibility: any, + ): void => { + // set fake credential when updating a cloud storage and cursor disappears from the field and value not changed + if (cloudStorage && !form.getFieldValue(key)) { + form.setFieldsValue({ + [key]: fakeCredentialsData[credential], + }); + setVisibility(false); + } + }; + + const onChangeCredentialsType = (value: CredentialsType): void => { + setCredentialsType(value); + resetCredentialsValues(); + }; + + const onSelectRegion = (key: string): void => { + setSelectedRegion(key); + }; + + const commonProps = { + className: 'cvat-cloud-storage-form-item', + labelCol: { span: 5 }, + wrapperCol: { offset: 1 }, + }; + + const credentialsBlok = (): JSX.Element => { + const internalCommonProps = { + ...commonProps, + labelCol: { span: 8, offset: 2 }, + wrapperCol: { offset: 1 }, + }; + + if (providerType === ProviderType.AWS_S3_BUCKET && credentialsType === CredentialsType.KEY_SECRET_KEY_PAIR) { + return ( + <> + + setKeyVisibility(true)} + onFocus={() => onFocusCredentialsItem('key', 'key')} + onBlur={() => onBlurCredentialsItem('key', 'key', setKeyVisibility)} + /> + + + setSecretKeyVisibility(true)} + onFocus={() => onFocusCredentialsItem('secretKey', 'secret_key')} + onBlur={() => onBlurCredentialsItem('secretKey', 'secret_key', setSecretKeyVisibility)} + /> + + + ); + } + + if ( + providerType === ProviderType.AZURE_CONTAINER && + credentialsType === CredentialsType.ACCOUNT_NAME_TOKEN_PAIR + ) { + return ( + <> + + setAccountNameVisibility(true)} + onFocus={() => onFocusCredentialsItem('accountName', 'account_name')} + onBlur={() => + onBlurCredentialsItem('accountName', 'account_name', setAccountNameVisibility)} + /> + + + setSessionTokenVisibility(true)} + onFocus={() => onFocusCredentialsItem('sessionToken', 'session_token')} + onBlur={() => + onBlurCredentialsItem('sessionToken', 'session_token', setSessionTokenVisibility)} + /> + + + ); + } + + if (providerType === ProviderType.AZURE_CONTAINER && credentialsType === CredentialsType.ANONYMOUS_ACCESS) { + return ( + <> + + setAccountNameVisibility(true)} + /> + + + ); + } + + return <>; + }; + + const AWSS3Configuration = (): JSX.Element => { + const internalCommonProps = { + ...commonProps, + labelCol: { span: 6, offset: 1 }, + wrapperCol: { offset: 1 }, + }; + + return ( + <> + + + + + + + {credentialsBlok()} + + + ); + }; + + const AzureBlobStorageConfiguration = (): JSX.Element => { + const internalCommonProps = { + ...commonProps, + labelCol: { span: 6, offset: 1 }, + wrapperCol: { offset: 1 }, + }; + + return ( + <> + + + + + + + + {credentialsBlok()} + + ); + }; + + return ( +
+ + + + +