parent
6df808dfce
commit
9a53879a8d
@ -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<CloudStoragesQuery>) =>
|
||||||
|
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<CloudStoragesQuery>,
|
||||||
|
) =>
|
||||||
|
createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_SUCCESS, {
|
||||||
|
array,
|
||||||
|
previews,
|
||||||
|
statuses,
|
||||||
|
count,
|
||||||
|
query,
|
||||||
|
}),
|
||||||
|
getCloudStoragesFailed: (error: any, query: Partial<CloudStoragesQuery>) =>
|
||||||
|
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<typeof cloudStoragesActions>;
|
||||||
|
|
||||||
|
export function getCloudStoragesAsync(query: Partial<CloudStoragesQuery>): ThunkAction {
|
||||||
|
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||||
|
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<Dispatch>): Promise<void> => {
|
||||||
|
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<Dispatch>): Promise<void> => {
|
||||||
|
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<Dispatch>): Promise<void> => {
|
||||||
|
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<Dispatch>): Promise<void> => {
|
||||||
|
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<Dispatch>): Promise<void> => {
|
||||||
|
// dispatch(cloudStoragesActions.getCloudStorageStatus());
|
||||||
|
// try {
|
||||||
|
// const result = await cloudStorage.getStatus();
|
||||||
|
// dispatch(cloudStoragesActions.getCloudStorageStatusSuccess(cloudStorage.id, result));
|
||||||
|
// } catch (error) {
|
||||||
|
// dispatch(cloudStoragesActions.getCloudStorageStatusFailed(cloudStorage.id, error));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0)">
|
||||||
|
<path d="M1.289 2.771L0 3.30333V12.6463L1.289 13.1755L1.29675 13.1678V2.77833L1.289 2.771Z" fill="#8C3123"/>
|
||||||
|
<path d="M8.18756 11.8195L1.28906 13.1755V2.771L8.18756 4.0975V11.8195Z" fill="#E05243"/>
|
||||||
|
<path d="M5.07349 9.69626L7.99961 10.0039L8.01798 9.96888L8.03442 6.00655L7.99961 5.97559L5.07349 6.27876V9.69626Z" fill="#8C3123"/>
|
||||||
|
<path d="M7.99976 11.8347L14.7104 13.1784L14.721 13.1645L14.7208 2.78029L14.7102 2.771L7.99976 4.11273V11.8347Z" fill="#8C3123"/>
|
||||||
|
<path d="M10.9267 9.69626L7.99976 10.0039V5.97559L10.9267 6.27876V9.69626Z" fill="#E05243"/>
|
||||||
|
<path d="M10.9265 4.62622L7.99961 5.06674L5.07349 4.62622L7.99592 3.99365L10.9265 4.62622Z" fill="#5E1F18"/>
|
||||||
|
<path d="M10.9265 11.3448L7.99961 10.9014L5.07349 11.3448L7.99605 12.0185L10.9265 11.3448Z" fill="#F2B0A9"/>
|
||||||
|
<path d="M5.07349 4.62612L7.99961 4.02813L8.0233 4.02209V0.0161548L7.99961 0L5.07349 1.20841V4.62612Z" fill="#8C3123"/>
|
||||||
|
<path d="M10.9267 4.62612L7.99976 4.02813V0L10.9267 1.20841V4.62612Z" fill="#E05243"/>
|
||||||
|
<path d="M7.99968 15.9704L5.07324 14.7624V11.3447L7.99968 11.9425L8.04274 11.9829L8.03106 15.9006L7.99968 15.9704Z" fill="#8C3123"/>
|
||||||
|
<path d="M7.99976 15.9704L10.9264 14.7624V11.3447L7.99976 11.9425V15.9704Z" fill="#E05243"/>
|
||||||
|
<path d="M14.7104 2.771L16 3.30333V12.6463L14.7104 13.1784V2.771Z" fill="#E05243"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0">
|
||||||
|
<rect width="16" height="16" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="1em" height="1em" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14.98 25.9939C18.55 25.2679 21.498 24.6669 21.532 24.6589L21.594 24.6439L18.224 20.0289C16.37 17.4909 14.854 15.4039 14.854 15.3919C14.854 15.3799 18.334 4.3359 18.354 4.2969C18.361 4.2839 20.729 8.9909 24.095 15.7079L29.869 27.2289L29.913 27.3169H8.49097L14.98 25.9939Z" fill="#0089D6"/>
|
||||||
|
<path d="M2.125 24.586C2.125 24.58 3.713 21.406 5.654 17.533L9.183 10.492L13.3 6.52002C15.562 4.33502 17.419 2.54402 17.426 2.54102C17.4112 2.60727 17.3891 2.67167 17.36 2.73302L12.89 13.759L8.5 24.589H5.311C4.24902 24.5999 3.18696 24.5989 2.125 24.586V24.586Z" fill="#0089D6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 683 B |
@ -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 (
|
||||||
|
<Card
|
||||||
|
cover={(
|
||||||
|
<>
|
||||||
|
{preview ? (
|
||||||
|
<img
|
||||||
|
className='cvat-cloud-storage-item-preview'
|
||||||
|
src={preview}
|
||||||
|
alt='Preview image'
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className='cvat-cloud-storage-item-empty-preview' aria-hidden>
|
||||||
|
<CloudSyncOutlined />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{description ? (
|
||||||
|
<CVATTooltip overlay={description}>
|
||||||
|
<QuestionCircleOutlined className='cvat-cloud-storage-description-icon' />
|
||||||
|
</CVATTooltip>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
size='small'
|
||||||
|
style={style}
|
||||||
|
className='cvat-cloud-storage-item'
|
||||||
|
>
|
||||||
|
<Meta
|
||||||
|
title={(
|
||||||
|
<Paragraph>
|
||||||
|
<Text strong>{`#${id}: `}</Text>
|
||||||
|
<Text>{displayName}</Text>
|
||||||
|
</Paragraph>
|
||||||
|
)}
|
||||||
|
description={(
|
||||||
|
<>
|
||||||
|
<Paragraph>
|
||||||
|
<Text type='secondary'>Provider: </Text>
|
||||||
|
<Text>{providerType}</Text>
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
<Text type='secondary'>Created </Text>
|
||||||
|
{owner ? <Text type='secondary'>{`by ${owner.username}`}</Text> : null}
|
||||||
|
<Text type='secondary'> on </Text>
|
||||||
|
<Text type='secondary'>{moment(createdDate).format('MMMM Do YYYY')}</Text>
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
<Text type='secondary'>Last updated </Text>
|
||||||
|
<Text type='secondary'>{moment(updatedDate).fromNow()}</Text>
|
||||||
|
</Paragraph>
|
||||||
|
<Status status={status} />
|
||||||
|
<Dropdown
|
||||||
|
overlay={(
|
||||||
|
<Menu className='cvat-project-actions-menu'>
|
||||||
|
<Menu.Item onClick={onUpdate}>Update</Menu.Item>
|
||||||
|
<Menu.Item onClick={onDelete}>Delete</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className='cvat-cloud-storage-item-menu-button'
|
||||||
|
type='link'
|
||||||
|
size='large'
|
||||||
|
icon={<MoreOutlined />}
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Paragraph from 'antd/lib/typography/Paragraph';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
import { StorageStatuses } from '../../utils/enums';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Status(props: Props): JSX.Element {
|
||||||
|
const { status } = props;
|
||||||
|
// TODO: make dynamic loading of statuses separately in the future
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paragraph>
|
||||||
|
<Text type='secondary'>Status: </Text>
|
||||||
|
{status ? (
|
||||||
|
<Text type={status === StorageStatuses.AVAILABLE ? 'success' : 'danger'}>{status}</Text>
|
||||||
|
) : (
|
||||||
|
<Text type='warning'>Loading ...</Text>
|
||||||
|
)}
|
||||||
|
</Paragraph>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Pagination from 'antd/lib/pagination';
|
||||||
|
import { Row, Col } from 'antd/lib/grid';
|
||||||
|
|
||||||
|
import { CloudStorage } from 'reducers/interfaces';
|
||||||
|
import CloudStorageItemComponent from './cloud-storage-item';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
storages: CloudStorage[];
|
||||||
|
previews: string[];
|
||||||
|
statuses: string[];
|
||||||
|
totalCount: number;
|
||||||
|
page: number;
|
||||||
|
onChangePage(page: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StoragesList(props: Props): JSX.Element {
|
||||||
|
const {
|
||||||
|
storages, previews, statuses, totalCount, page, onChangePage,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const groupedStorages = storages.reduce(
|
||||||
|
(acc: CloudStorage[][], storage: CloudStorage, index: number): CloudStorage[][] => {
|
||||||
|
if (index && index % 4) {
|
||||||
|
acc[acc.length - 1].push({
|
||||||
|
storage,
|
||||||
|
preview: previews[index],
|
||||||
|
status: statuses[index],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
acc.push([{
|
||||||
|
storage,
|
||||||
|
preview: previews[index],
|
||||||
|
status: statuses[index],
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row justify='center' align='middle'>
|
||||||
|
<Col span={24} className='cvat-cloud-storages-list'>
|
||||||
|
{groupedStorages.map(
|
||||||
|
(instances: CloudStorage[]): JSX.Element => (
|
||||||
|
<Row key={instances[0].storage.id} gutter={[8, 8]}>
|
||||||
|
{instances.map((instance: CloudStorage) => (
|
||||||
|
<Col span={6} key={instance.storage.id}>
|
||||||
|
<CloudStorageItemComponent cloudStorageInstance={instance} />
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row justify='center' align='middle'>
|
||||||
|
<Col>
|
||||||
|
<Pagination
|
||||||
|
className='cvat-cloud-storages-pagination'
|
||||||
|
onChange={onChangePage}
|
||||||
|
showSizeChanger={false}
|
||||||
|
total={totalCount}
|
||||||
|
pageSize={12}
|
||||||
|
current={page}
|
||||||
|
showQuickJumper
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import { Row, Col } from 'antd/lib/grid';
|
||||||
|
import Spin from 'antd/lib/spin';
|
||||||
|
|
||||||
|
import { CloudStorage, CloudStoragesQuery, CombinedState } from 'reducers/interfaces';
|
||||||
|
import { getCloudStoragesAsync } from 'actions/cloud-storage-actions';
|
||||||
|
import CloudStoragesListComponent from './cloud-storages-list';
|
||||||
|
import EmptyCloudStorageListComponent from './empty-cloud-storages-list';
|
||||||
|
import TopBarComponent from './top-bar';
|
||||||
|
|
||||||
|
export default function StoragesPageComponent(): JSX.Element {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const history = useHistory();
|
||||||
|
const { search } = history.location;
|
||||||
|
const totalCount = useSelector((state: CombinedState) => state.cloudStorages.count);
|
||||||
|
const isFetching = useSelector((state: CombinedState) => state.cloudStorages.fetching);
|
||||||
|
const current = useSelector((state: CombinedState) => state.cloudStorages.current)
|
||||||
|
.map((cloudStrage: CloudStorage) => cloudStrage.instance);
|
||||||
|
const previews = useSelector((state: CombinedState) => state.cloudStorages.current)
|
||||||
|
.map((cloudStrage: CloudStorage) => cloudStrage.preview as string);
|
||||||
|
const statuses = useSelector((state: CombinedState) => state.cloudStorages.current)
|
||||||
|
.map((cloudStrage: CloudStorage) => cloudStrage.status as string);
|
||||||
|
const query = useSelector((state: CombinedState) => state.cloudStorages.gettingQuery);
|
||||||
|
const onSearch = useCallback(
|
||||||
|
(_query: CloudStoragesQuery) => {
|
||||||
|
if (!isFetching) dispatch(getCloudStoragesAsync(_query));
|
||||||
|
},
|
||||||
|
[isFetching],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangePage = useCallback(
|
||||||
|
(page: number) => {
|
||||||
|
if (!isFetching && page !== query.page) dispatch(getCloudStoragesAsync({ ...query, page }));
|
||||||
|
},
|
||||||
|
[query],
|
||||||
|
);
|
||||||
|
|
||||||
|
const dimensions = {
|
||||||
|
md: 22,
|
||||||
|
lg: 18,
|
||||||
|
xl: 16,
|
||||||
|
xxl: 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
for (const [key, value] of Object.entries(query)) {
|
||||||
|
if (value !== null && typeof value !== 'undefined') {
|
||||||
|
searchParams.append(key, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
pathname: '/cloudstorages',
|
||||||
|
search: `?${searchParams.toString()}`,
|
||||||
|
});
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const searchParams = { ...query };
|
||||||
|
for (const [key, value] of new URLSearchParams(search)) {
|
||||||
|
if (key in searchParams) {
|
||||||
|
searchParams[key] = ['page', 'id'].includes(key) ? +value : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onSearch(searchParams);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const searchWasUsed = Object.entries(query).some(([key, value]) => {
|
||||||
|
if (key === 'page') {
|
||||||
|
return value && Number.isInteger(value) && value > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isFetching) {
|
||||||
|
return (
|
||||||
|
<Row className='cvat-cloud-storages-page' justify='center' align='middle'>
|
||||||
|
<Spin size='large' />
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row className='cvat-cloud-storages-page' justify='center' align='top'>
|
||||||
|
<Col {...dimensions}>
|
||||||
|
<TopBarComponent query={query} onSearch={onSearch} />
|
||||||
|
{current.length ? (
|
||||||
|
<CloudStoragesListComponent
|
||||||
|
totalCount={totalCount}
|
||||||
|
page={query.page}
|
||||||
|
storages={current}
|
||||||
|
previews={previews}
|
||||||
|
statuses={statuses}
|
||||||
|
onChangePage={onChangePage}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyCloudStorageListComponent notFound={searchWasUsed} />
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Empty from 'antd/lib/empty';
|
||||||
|
import { Row, Col } from 'antd/lib/grid';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
import { CloudTwoTone } from '@ant-design/icons';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
notFound: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EmptyStoragesListComponent(props: Props): JSX.Element {
|
||||||
|
const { notFound } = props;
|
||||||
|
|
||||||
|
const description = notFound ? (
|
||||||
|
<Row justify='center' align='middle'>
|
||||||
|
<Col>
|
||||||
|
<Text strong>No results matched your search found...</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Row justify='center' align='middle'>
|
||||||
|
<Col>
|
||||||
|
<Text strong>No cloud storages attached yet ...</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row justify='center' align='middle'>
|
||||||
|
<Col>
|
||||||
|
<Text type='secondary'>To get started with your cloud storage</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row justify='center' align='middle'>
|
||||||
|
<Col>
|
||||||
|
<Link to='/cloudstorages/create'>attach a new one</Link>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='cvat-empty-cloud-storages-list'>
|
||||||
|
<Empty description={description} image={<CloudTwoTone className='cvat-empty-cloud-storages-list-icon' />} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
@import '../../base.scss';
|
||||||
|
|
||||||
|
.cvat-cloud-storages-page {
|
||||||
|
padding: $grid-unit-size * 2;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.ant-spin {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-empty-cloud-storages-list-icon {
|
||||||
|
font-size: $grid-unit-size * 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storages-pagination {
|
||||||
|
margin-top: $grid-unit-size * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storages-list-top-bar {
|
||||||
|
> div:first-child {
|
||||||
|
.cvat-title {
|
||||||
|
margin-right: $grid-unit-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:last-child {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storages-list,
|
||||||
|
.cvat-empty-cloud-storages-list {
|
||||||
|
margin-top: $grid-unit-size * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storage-item {
|
||||||
|
div.ant-typography {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storage-item-empty-preview {
|
||||||
|
font-size: $grid-unit-size * 15;
|
||||||
|
text-align: center;
|
||||||
|
height: $grid-unit-size * 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: $grid-unit-size * 24;
|
||||||
|
width: auto;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storage-item-menu-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storage-description-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: $grid-unit-size;
|
||||||
|
width: auto;
|
||||||
|
right: $grid-unit-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import { Row, Col } from 'antd/lib/grid';
|
||||||
|
import Button from 'antd/lib/button';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import SearchField from 'components/search-field/search-field';
|
||||||
|
import { CloudStoragesQuery } from 'reducers/interfaces';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onSearch(query: CloudStoragesQuery): void;
|
||||||
|
query: CloudStoragesQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StoragesTopBar(props: Props): JSX.Element {
|
||||||
|
const { onSearch, query } = props;
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row justify='space-between' align='middle' className='cvat-cloud-storages-list-top-bar'>
|
||||||
|
<Col md={11} lg={9} xl={9} xxl={9}>
|
||||||
|
<Text className='cvat-title'>Cloud Storages</Text>
|
||||||
|
<SearchField instance='cloudstorage' onSearch={onSearch} query={query} />
|
||||||
|
</Col>
|
||||||
|
<Col md={{ span: 11 }} lg={{ span: 9 }} xl={{ span: 9 }} xxl={{ span: 9 }}>
|
||||||
|
<Button
|
||||||
|
size='large'
|
||||||
|
className='cvat-attach-cloud-storage-button'
|
||||||
|
type='primary'
|
||||||
|
onClick={(): void => history.push('/cloudstorages/create')}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
Attach a new storage
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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<ProviderType | null>(null);
|
||||||
|
const [credentialsType, setCredentialsType] = useState<CredentialsType | null>(null);
|
||||||
|
const [selectedRegion, setSelectedRegion] = useState<string | undefined>(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<string[]>([]);
|
||||||
|
|
||||||
|
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<void> => {
|
||||||
|
let cloudStorageData: Record<string, any> = {};
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label='ACCESS KEY ID'
|
||||||
|
name='key'
|
||||||
|
rules={[{ required: true, message: 'Please, specify your access_key_id' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
maxLength={20}
|
||||||
|
visibilityToggle={keyVisibility}
|
||||||
|
onChange={() => setKeyVisibility(true)}
|
||||||
|
onFocus={() => onFocusCredentialsItem('key', 'key')}
|
||||||
|
onBlur={() => onBlurCredentialsItem('key', 'key', setKeyVisibility)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label='SECRET ACCESS KEY ID'
|
||||||
|
name='secret_key'
|
||||||
|
rules={[{ required: true, message: 'Please, specify your secret_access_key_id' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
maxLength={40}
|
||||||
|
visibilityToggle={secretKeyVisibility}
|
||||||
|
onChange={() => setSecretKeyVisibility(true)}
|
||||||
|
onFocus={() => onFocusCredentialsItem('secretKey', 'secret_key')}
|
||||||
|
onBlur={() => onBlurCredentialsItem('secretKey', 'secret_key', setSecretKeyVisibility)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
providerType === ProviderType.AZURE_CONTAINER &&
|
||||||
|
credentialsType === CredentialsType.ACCOUNT_NAME_TOKEN_PAIR
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label='Account name'
|
||||||
|
name='account_name'
|
||||||
|
rules={[{ required: true, message: 'Please, specify your account name' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
minLength={3}
|
||||||
|
maxLength={24}
|
||||||
|
visibilityToggle={accountNameVisibility}
|
||||||
|
onChange={() => setAccountNameVisibility(true)}
|
||||||
|
onFocus={() => onFocusCredentialsItem('accountName', 'account_name')}
|
||||||
|
onBlur={() =>
|
||||||
|
onBlurCredentialsItem('accountName', 'account_name', setAccountNameVisibility)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label='SAS token'
|
||||||
|
name='SAS_token'
|
||||||
|
rules={[{ required: true, message: 'Please, specify your SAS token' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
visibilityToggle={sessionTokenVisibility}
|
||||||
|
maxLength={437}
|
||||||
|
onChange={() => setSessionTokenVisibility(true)}
|
||||||
|
onFocus={() => onFocusCredentialsItem('sessionToken', 'session_token')}
|
||||||
|
onBlur={() =>
|
||||||
|
onBlurCredentialsItem('sessionToken', 'session_token', setSessionTokenVisibility)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerType === ProviderType.AZURE_CONTAINER && credentialsType === CredentialsType.ANONYMOUS_ACCESS) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label='Account name'
|
||||||
|
name='account_name'
|
||||||
|
rules={[{ required: true, message: 'Please, specify your account name' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Input.Password
|
||||||
|
minLength={3}
|
||||||
|
maxLength={24}
|
||||||
|
visibilityToggle={accountNameVisibility}
|
||||||
|
onChange={() => setAccountNameVisibility(true)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AWSS3Configuration = (): JSX.Element => {
|
||||||
|
const internalCommonProps = {
|
||||||
|
...commonProps,
|
||||||
|
labelCol: { span: 6, offset: 1 },
|
||||||
|
wrapperCol: { offset: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label='Bucket name'
|
||||||
|
name='resource'
|
||||||
|
rules={[{ required: true, message: 'Please, specify a bucket name' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Input disabled={!!cloudStorage} maxLength={63} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label='Authorization type'
|
||||||
|
name='credentials_type'
|
||||||
|
rules={[{ required: true, message: 'Please, specify credentials type' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Select onSelect={(value: CredentialsType) => onChangeCredentialsType(value)}>
|
||||||
|
<Select.Option value={CredentialsType.KEY_SECRET_KEY_PAIR}>
|
||||||
|
Key id and secret access key pair
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value={CredentialsType.ANONYMOUS_ACCESS}>Anonymous access</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
{credentialsBlok()}
|
||||||
|
<S3Region
|
||||||
|
selectedRegion={selectedRegion}
|
||||||
|
onSelectRegion={onSelectRegion}
|
||||||
|
internalCommonProps={internalCommonProps}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AzureBlobStorageConfiguration = (): JSX.Element => {
|
||||||
|
const internalCommonProps = {
|
||||||
|
...commonProps,
|
||||||
|
labelCol: { span: 6, offset: 1 },
|
||||||
|
wrapperCol: { offset: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label='Container name'
|
||||||
|
name='resource'
|
||||||
|
rules={[{ required: true, message: 'Please, specify a container name' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Input disabled={!!cloudStorage} maxLength={63} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label='Authorization type'
|
||||||
|
name='credentials_type'
|
||||||
|
rules={[{ required: true, message: 'Please, specify credentials type' }]}
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Select onSelect={(value: CredentialsType) => onChangeCredentialsType(value)}>
|
||||||
|
<Select.Option value={CredentialsType.ACCOUNT_NAME_TOKEN_PAIR}>
|
||||||
|
Account name and SAS token
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value={CredentialsType.ANONYMOUS_ACCESS}>Anonymous access</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{credentialsBlok()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form className='cvat-cloud-storage-form' layout='horizontal' form={form}>
|
||||||
|
<Form.Item
|
||||||
|
{...commonProps}
|
||||||
|
label='Display name'
|
||||||
|
name='display_name'
|
||||||
|
rules={[{ required: true, message: 'Please, specify a display name' }]}
|
||||||
|
>
|
||||||
|
<Input maxLength={63} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...commonProps} label='Description' name='description'>
|
||||||
|
<TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder='Any useful description' />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
{...commonProps}
|
||||||
|
label='Provider'
|
||||||
|
name='provider_type'
|
||||||
|
rules={[{ required: true, message: 'Please, specify a cloud storage provider' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
disabled={!!cloudStorage}
|
||||||
|
onSelect={(value: ProviderType) => {
|
||||||
|
setProviderType(value);
|
||||||
|
setCredentialsType(null);
|
||||||
|
form.resetFields(['credentials_type']);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Option value={ProviderType.AWS_S3_BUCKET}>
|
||||||
|
<span className='cvat-cloud-storage-select-provider'>
|
||||||
|
<S3Provider />
|
||||||
|
AWS S3
|
||||||
|
</span>
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value={ProviderType.AZURE_CONTAINER}>
|
||||||
|
<span className='cvat-cloud-storage-select-provider'>
|
||||||
|
<AzureProvider />
|
||||||
|
Azure Blob Container
|
||||||
|
</span>
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
{providerType === ProviderType.AWS_S3_BUCKET && AWSS3Configuration()}
|
||||||
|
{providerType === ProviderType.AZURE_CONTAINER && AzureBlobStorageConfiguration()}
|
||||||
|
<ManifestsManager form={form} manifestNames={manifestNames} setManifestNames={setManifestNames} />
|
||||||
|
<Row justify='end'>
|
||||||
|
<Col>
|
||||||
|
<Button
|
||||||
|
htmlType='button'
|
||||||
|
onClick={() => onCancel()}
|
||||||
|
className='cvat-cloud-storage-reset-button'
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
<Col offset={1}>
|
||||||
|
<Button
|
||||||
|
type='primary'
|
||||||
|
htmlType='submit'
|
||||||
|
onClick={onSubmit}
|
||||||
|
className='cvat-cloud-storage-submit-button'
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{cloudStorage ? 'Update' : 'Submit'}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React from 'react';
|
||||||
|
import { Row, Col } from 'antd/lib/grid';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
|
||||||
|
import CreateCloudStorageForm from './cloud-storage-form';
|
||||||
|
|
||||||
|
export default function CreateCloudStoragePageComponent(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Row justify='center' align='top' className='cvat-attach-cloud-storage-form-wrapper'>
|
||||||
|
<Col md={20} lg={16} xl={14} xxl={9}>
|
||||||
|
<Text className='cvat-title'>Create a cloud storage</Text>
|
||||||
|
<CreateCloudStorageForm />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { MinusCircleOutlined, PlusCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
|
import Button from 'antd/lib/button';
|
||||||
|
import Col from 'antd/lib/col';
|
||||||
|
import Form from 'antd/lib/form';
|
||||||
|
import Input from 'antd/lib/input';
|
||||||
|
import Row from 'antd/lib/row';
|
||||||
|
import notification from 'antd/lib/notification';
|
||||||
|
import Tooltip from 'antd/lib/tooltip';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
form: any;
|
||||||
|
manifestNames: string[];
|
||||||
|
setManifestNames: (manifestNames: string[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ManifestsManager(props: Props): JSX.Element {
|
||||||
|
const { form, manifestNames, setManifestNames } = props;
|
||||||
|
const maxManifestsCount = useRef(5);
|
||||||
|
const [limitingAddingManifestNotification, setLimitingAddingManifestNotification] = useState(false);
|
||||||
|
|
||||||
|
const updateManifestFields = (): void => {
|
||||||
|
const newManifestFormItems = manifestNames.map((name, idx) => ({
|
||||||
|
id: idx,
|
||||||
|
name,
|
||||||
|
}));
|
||||||
|
form.setFieldsValue({
|
||||||
|
manifests: [...newManifestFormItems],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateManifestFields();
|
||||||
|
}, [manifestNames]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (limitingAddingManifestNotification) {
|
||||||
|
notification.warning({
|
||||||
|
message: `Unable to add manifest. The maximum number of files is ${maxManifestsCount.current}`,
|
||||||
|
className: 'cvat-notification-limiting-adding-manifest',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [limitingAddingManifestNotification]);
|
||||||
|
|
||||||
|
const onChangeManifestPath = (manifestName: string | undefined, manifestId: number): void => {
|
||||||
|
if (manifestName !== undefined) {
|
||||||
|
setManifestNames(manifestNames.map((name, idx) => (idx !== manifestId ? name : manifestName)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteManifestItem = (key: number): void => {
|
||||||
|
if (maxManifestsCount.current === manifestNames.length && limitingAddingManifestNotification) {
|
||||||
|
setLimitingAddingManifestNotification(false);
|
||||||
|
}
|
||||||
|
setManifestNames(manifestNames.filter((name, idx) => idx !== key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAddManifestItem = (): void => {
|
||||||
|
if (maxManifestsCount.current <= manifestNames.length) {
|
||||||
|
setLimitingAddingManifestNotification(true);
|
||||||
|
} else {
|
||||||
|
setManifestNames(manifestNames.concat(['']));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name='manifests'
|
||||||
|
label={(
|
||||||
|
<>
|
||||||
|
Manifests
|
||||||
|
<Tooltip title='More information'>
|
||||||
|
<Button
|
||||||
|
type='link'
|
||||||
|
target='_blank'
|
||||||
|
className='cvat-cloud-storage-help-button'
|
||||||
|
href='https://openvinotoolkit.github.io/cvat/docs/manual/advanced/dataset_manifest/'
|
||||||
|
>
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
rules={[{ required: true, message: 'Please, specify at least one manifest file' }]}
|
||||||
|
/>
|
||||||
|
<Form.List name='manifests'>
|
||||||
|
{
|
||||||
|
(fields) => (
|
||||||
|
<>
|
||||||
|
{fields.map((field, idx): JSX.Element => (
|
||||||
|
<Form.Item key={idx} shouldUpdate>
|
||||||
|
<Row justify='space-between' align='top'>
|
||||||
|
<Col>
|
||||||
|
<Form.Item
|
||||||
|
name={[idx, 'name']}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: 'Please specify a manifest name',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
initialValue={field.name}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder='manifest.jsonl'
|
||||||
|
onChange={(event) => onChangeManifestPath(event.target.value, idx)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type='link' onClick={() => onDeleteManifestItem(idx)}>
|
||||||
|
<MinusCircleOutlined />
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Form.List>
|
||||||
|
<Row justify='start'>
|
||||||
|
<Col>
|
||||||
|
<Button type='ghost' onClick={onAddManifestItem} className='cvat-add-manifest-button'>
|
||||||
|
Add manifest
|
||||||
|
<PlusCircleOutlined />
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import Divider from 'antd/lib/divider';
|
||||||
|
import Select from 'antd/lib/select';
|
||||||
|
import { PlusCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
|
import Input from 'antd/lib/input';
|
||||||
|
import Button from 'antd/lib/button';
|
||||||
|
import Form from 'antd/lib/form';
|
||||||
|
import notification from 'antd/lib/notification';
|
||||||
|
import Tooltip from 'antd/lib/tooltip';
|
||||||
|
import consts from '../../consts';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
interface Props {
|
||||||
|
selectedRegion: undefined | string;
|
||||||
|
onSelectRegion: any;
|
||||||
|
internalCommonProps: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareDefaultRegions(): Map<string, string> {
|
||||||
|
const temp = new Map<string, string>();
|
||||||
|
for (const [key, value] of consts.DEFAULT_AWS_S3_REGIONS) {
|
||||||
|
temp.set(key, value);
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function S3Region(props: Props): JSX.Element {
|
||||||
|
const { selectedRegion, onSelectRegion, internalCommonProps } = props;
|
||||||
|
const [regions, setRegions] = useState<Map<string, string>>(() => prepareDefaultRegions());
|
||||||
|
const [newRegionKey, setNewRegionKey] = useState<string>('');
|
||||||
|
const [newRegionName, setNewRegionName] = useState<string>('');
|
||||||
|
|
||||||
|
const handleAddingRegion = (): void => {
|
||||||
|
if (!newRegionKey || !newRegionName) {
|
||||||
|
notification.warning({
|
||||||
|
message: 'Incorrect region',
|
||||||
|
className: 'cvat-incorrect-add-region-notification',
|
||||||
|
});
|
||||||
|
} else if (regions.has(newRegionKey)) {
|
||||||
|
notification.warning({
|
||||||
|
message: 'This region already exists',
|
||||||
|
className: 'cvat-incorrect-add-region-notification',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const regionsCopy = regions;
|
||||||
|
setRegions(regionsCopy.set(newRegionKey, newRegionName));
|
||||||
|
setNewRegionKey('');
|
||||||
|
setNewRegionName('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
label={(
|
||||||
|
<>
|
||||||
|
Region
|
||||||
|
<Tooltip title='More information'>
|
||||||
|
<Button
|
||||||
|
className='cvat-cloud-storage-help-button'
|
||||||
|
type='link'
|
||||||
|
target='_blank'
|
||||||
|
href='https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions'
|
||||||
|
>
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
name='region'
|
||||||
|
{...internalCommonProps}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder='Select region'
|
||||||
|
defaultValue={selectedRegion ? regions.get(selectedRegion) : undefined}
|
||||||
|
dropdownRender={(menu) => (
|
||||||
|
<div>
|
||||||
|
{menu}
|
||||||
|
<Divider className='cvat-divider' />
|
||||||
|
<div className='cvat-cloud-storage-region-creator'>
|
||||||
|
<Input
|
||||||
|
value={newRegionKey}
|
||||||
|
onChange={(event: any) => setNewRegionKey(event.target.value)}
|
||||||
|
maxLength={14}
|
||||||
|
placeholder='key'
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
value={newRegionName}
|
||||||
|
onChange={(event: any) => setNewRegionName(event.target.value)}
|
||||||
|
placeholder='name'
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type='link'
|
||||||
|
onClick={handleAddingRegion}
|
||||||
|
>
|
||||||
|
Add region
|
||||||
|
<PlusCircleOutlined />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
onSelect={(_, instance) => onSelectRegion(instance.key)}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
Array.from(regions.entries()).map(
|
||||||
|
([key, value]): JSX.Element => (
|
||||||
|
<Option key={key} value={value}>
|
||||||
|
{value}
|
||||||
|
</Option>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
@import '../../base.scss';
|
||||||
|
|
||||||
|
.cvat-update-cloud-storage-form-wrapper,
|
||||||
|
.cvat-attach-cloud-storage-form-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: $grid-unit-size * 5;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 90%;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> div > span {
|
||||||
|
font-size: $grid-unit-size * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storage-form {
|
||||||
|
margin-top: $grid-unit-size * 2;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border: 1px solid $border-color-1;
|
||||||
|
border-radius: $grid-unit-size / 2;
|
||||||
|
padding: $grid-unit-size * 2;
|
||||||
|
background: $background-color-1;
|
||||||
|
text-align: initial;
|
||||||
|
|
||||||
|
.cvat-cloud-storage-form-item {
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> div:first-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:not(first-child) {
|
||||||
|
margin-top: $grid-unit-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-attach-cloud-storage-reset-button,
|
||||||
|
.cvat-attach-cloud-storage-submit-button {
|
||||||
|
width: $grid-unit-size * 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storage-region-creator {
|
||||||
|
display: flex;
|
||||||
|
padding: $grid-unit-size;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin: 0 $grid-unit-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-cloud-storage-help-button {
|
||||||
|
padding-left: $grid-unit-size * 0.5;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
@ -0,0 +1,239 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React, { ReactText, useEffect, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { Row } from 'antd/lib/grid';
|
||||||
|
import Tree from 'antd/lib/tree/Tree';
|
||||||
|
import Spin from 'antd/lib/spin';
|
||||||
|
import Alert from 'antd/lib/alert';
|
||||||
|
import Empty from 'antd/lib/empty';
|
||||||
|
import { EventDataNode } from 'antd/lib/tree';
|
||||||
|
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
|
||||||
|
import Divider from 'antd/lib/divider';
|
||||||
|
import { CloudStorage, CombinedState } from 'reducers/interfaces';
|
||||||
|
import { loadCloudStorageContentAsync } from 'actions/cloud-storage-actions';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
cloudStorage: CloudStorage;
|
||||||
|
selectedManifest: string;
|
||||||
|
onSelectFiles: (checkedKeysValue: string[]) => void;
|
||||||
|
selectedFiles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataNode {
|
||||||
|
title: string;
|
||||||
|
key: string;
|
||||||
|
isLeaf: boolean;
|
||||||
|
disabled: boolean;
|
||||||
|
children: DataNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataStructure {
|
||||||
|
name: string;
|
||||||
|
children: Map<string, DataStructure> | null;
|
||||||
|
unparsedChildren: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Files =
|
||||||
|
| ReactText[]
|
||||||
|
| {
|
||||||
|
checked: ReactText[];
|
||||||
|
halfChecked: ReactText[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CloudStorageFiles(props: Props): JSX.Element {
|
||||||
|
const {
|
||||||
|
cloudStorage, selectedManifest, selectedFiles, onSelectFiles,
|
||||||
|
} = props;
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const isFetching = useSelector((state: CombinedState) => state.cloudStorages.activities.contentLoads.fetching);
|
||||||
|
const content = useSelector((state: CombinedState) => state.cloudStorages.activities.contentLoads.content);
|
||||||
|
const error = useSelector((state: CombinedState) => state.cloudStorages.activities.contentLoads.error);
|
||||||
|
const [treeData, setTreeData] = useState<DataNode[]>([]);
|
||||||
|
const [initialData, setInitialData] = useState<DataStructure>({
|
||||||
|
name: 'root',
|
||||||
|
children: null,
|
||||||
|
unparsedChildren: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [checkedAll, setCheckedAll] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(loadCloudStorageContentAsync(cloudStorage));
|
||||||
|
}, [cloudStorage.id, selectedManifest]);
|
||||||
|
|
||||||
|
const parseContent = (mass: string[], root = ''): Map<string, DataStructure> => {
|
||||||
|
const data: Map<string, DataStructure> = new Map();
|
||||||
|
// define directories
|
||||||
|
const upperDirs: Set<string> = new Set(
|
||||||
|
mass.filter((path: string) => path.includes('/')).map((path: string) => path.split('/', 1)[0]),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const dir of upperDirs) {
|
||||||
|
const child: DataStructure = {
|
||||||
|
name: dir,
|
||||||
|
children: null,
|
||||||
|
unparsedChildren: mass
|
||||||
|
.filter((path: string) => path.startsWith(`${dir}/`))
|
||||||
|
.map((path: string) => path.replace(`${dir}/`, '')),
|
||||||
|
};
|
||||||
|
data.set(`${root}${dir}/`, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// define files
|
||||||
|
const rootFiles = mass.filter((path: string) => !path.includes('/'));
|
||||||
|
|
||||||
|
for (const rootFile of rootFiles) {
|
||||||
|
const child: DataStructure = {
|
||||||
|
name: rootFile,
|
||||||
|
children: null,
|
||||||
|
unparsedChildren: null,
|
||||||
|
};
|
||||||
|
data.set(`${root}${rootFile}`, child);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateData = (key: string, data: Map<string, DataStructure> | null): Map<string, DataStructure> | null => {
|
||||||
|
if (data === null) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [dataItemKey, dataItemValue] of data) {
|
||||||
|
if (key.startsWith(dataItemKey) && key.replace(dataItemKey, '')) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
data = updateData(key, dataItemValue.children);
|
||||||
|
} else if (dataItemKey === key) {
|
||||||
|
const unparsedDataItemChildren = dataItemValue.unparsedChildren;
|
||||||
|
if (dataItemValue && unparsedDataItemChildren) {
|
||||||
|
dataItemValue.children = parseContent(unparsedDataItemChildren, dataItemKey);
|
||||||
|
dataItemValue.unparsedChildren = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLoadData = (key: string): Promise<void> =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
if (initialData.children === null) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInitialData({
|
||||||
|
...initialData,
|
||||||
|
children: updateData(key, initialData.children),
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (content) {
|
||||||
|
const children = parseContent(content);
|
||||||
|
|
||||||
|
setInitialData({
|
||||||
|
...initialData,
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setInitialData({
|
||||||
|
name: 'root',
|
||||||
|
children: null,
|
||||||
|
unparsedChildren: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [content]);
|
||||||
|
|
||||||
|
const prepareNodes = (data: Map<string, DataStructure>, nodes: DataNode[]): DataNode[] => {
|
||||||
|
for (const [key, value] of data) {
|
||||||
|
const node: DataNode = {
|
||||||
|
title: value.name,
|
||||||
|
key,
|
||||||
|
isLeaf: !value.children && !value.unparsedChildren,
|
||||||
|
disabled: !!value.unparsedChildren,
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
if (value.children) {
|
||||||
|
node.children = prepareNodes(value.children, []);
|
||||||
|
}
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialData.children && content) {
|
||||||
|
const nodes = prepareNodes(initialData.children, []);
|
||||||
|
setTreeData(nodes);
|
||||||
|
} else {
|
||||||
|
setTreeData([]);
|
||||||
|
}
|
||||||
|
}, [initialData]);
|
||||||
|
|
||||||
|
const onChangeCheckedAll = (checked: boolean): void => {
|
||||||
|
setCheckedAll(checked);
|
||||||
|
if (checked) {
|
||||||
|
onSelectFiles((content as string[]).concat([selectedManifest]));
|
||||||
|
} else {
|
||||||
|
onSelectFiles([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isFetching) {
|
||||||
|
return (
|
||||||
|
<Row className='cvat-create-task-page-empty-cloud-storage' justify='center' align='middle'>
|
||||||
|
<Spin size='large' />
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
className='cvat-cloud-storage-alert-fetching-failed'
|
||||||
|
message='Could not fetch cloud storage data'
|
||||||
|
type='error'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{treeData.length ? (
|
||||||
|
<>
|
||||||
|
<Checkbox
|
||||||
|
className='cvat-cloud-storage-files-checkbox'
|
||||||
|
onChange={(event: CheckboxChangeEvent) => onChangeCheckedAll(event.target.checked)}
|
||||||
|
checked={checkedAll}
|
||||||
|
>
|
||||||
|
Select all
|
||||||
|
</Checkbox>
|
||||||
|
<Divider className='cvat-divider' />
|
||||||
|
<Tree.DirectoryTree
|
||||||
|
selectable={false}
|
||||||
|
multiple
|
||||||
|
checkable
|
||||||
|
height={256}
|
||||||
|
onCheck={(checkedKeys: Files) => {
|
||||||
|
const checkedFiles = (checkedKeys as string[]).filter((f) => !f.endsWith('/'));
|
||||||
|
if (checkedFiles.length === (content as string[]).length) {
|
||||||
|
setCheckedAll(true);
|
||||||
|
} else if (checkedAll) {
|
||||||
|
setCheckedAll(false);
|
||||||
|
}
|
||||||
|
onSelectFiles(checkedFiles.concat([selectedManifest]));
|
||||||
|
}}
|
||||||
|
loadData={(event: EventDataNode): Promise<void> => onLoadData(event.key.toLocaleString())}
|
||||||
|
treeData={treeData}
|
||||||
|
checkedKeys={selectedFiles}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Empty className='cvat-empty-cloud-storages-tree' description='The storage is empty' />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,188 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import Form from 'antd/lib/form';
|
||||||
|
import notification from 'antd/lib/notification';
|
||||||
|
import AutoComplete from 'antd/lib/auto-complete';
|
||||||
|
import Input from 'antd/lib/input';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
|
import Select from 'antd/lib/select';
|
||||||
|
import getCore from 'cvat-core-wrapper';
|
||||||
|
import { CloudStorage } from 'reducers/interfaces';
|
||||||
|
import { AzureProvider, S3Provider } from 'icons';
|
||||||
|
import { ProviderType } from 'utils/enums';
|
||||||
|
import CloudStorageFiles from './cloud-storages-files';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
formRef: any;
|
||||||
|
cloudStorage: CloudStorage | null;
|
||||||
|
searchPhrase: string;
|
||||||
|
setSearchPhrase: (searchPhrase: string) => void;
|
||||||
|
selectedFiles: string[];
|
||||||
|
onSelectFiles: (files: string[]) => void;
|
||||||
|
onSelectCloudStorage: (cloudStorageId: number | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchCloudStorages(filter: Record<string, string>): Promise<CloudStorage[]> {
|
||||||
|
try {
|
||||||
|
const data = await getCore().cloudStorages.get(filter);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: 'Could not fetch a list of cloud storages',
|
||||||
|
description: error.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const searchCloudStoragesWrapper = debounce((phrase, setList) => {
|
||||||
|
const filter = { displayName: phrase };
|
||||||
|
searchCloudStorages(filter).then((list) => {
|
||||||
|
setList(list);
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
export default function CloudStorageTab(props: Props): JSX.Element {
|
||||||
|
const { searchPhrase, setSearchPhrase } = props;
|
||||||
|
const [initialList, setInitialList] = useState<CloudStorage[]>([]);
|
||||||
|
const [list, setList] = useState<CloudStorage[]>([]);
|
||||||
|
const {
|
||||||
|
formRef, cloudStorage, selectedFiles, onSelectFiles, onSelectCloudStorage,
|
||||||
|
} = props;
|
||||||
|
const [selectedManifest, setSelectedManifest] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
searchCloudStorages({}).then((data) => {
|
||||||
|
setInitialList(data);
|
||||||
|
if (!list.length) {
|
||||||
|
setList(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!searchPhrase) {
|
||||||
|
setList(initialList);
|
||||||
|
} else {
|
||||||
|
searchCloudStoragesWrapper(searchPhrase, setList);
|
||||||
|
}
|
||||||
|
}, [searchPhrase, initialList]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cloudStorage) {
|
||||||
|
setSelectedManifest(cloudStorage.manifests[0]);
|
||||||
|
}
|
||||||
|
}, [cloudStorage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedManifest) {
|
||||||
|
cloudStorage.manifestPath = selectedManifest;
|
||||||
|
}
|
||||||
|
}, [selectedManifest]);
|
||||||
|
|
||||||
|
const onBlur = (): void => {
|
||||||
|
if (!searchPhrase && cloudStorage) {
|
||||||
|
onSelectCloudStorage(null);
|
||||||
|
} else if (searchPhrase) {
|
||||||
|
const potentialStorages = list.filter((_cloudStorage) => _cloudStorage.displayName.includes(searchPhrase));
|
||||||
|
if (potentialStorages.length === 1) {
|
||||||
|
const potentialStorage = potentialStorages[0];
|
||||||
|
setSearchPhrase(potentialStorage.displayName);
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
potentialStorage.manifestPath = potentialStorage.manifests[0];
|
||||||
|
onSelectCloudStorage(potentialStorage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form ref={formRef} className='cvat-create-task-page-cloud-storages-tab-form' layout='vertical'>
|
||||||
|
<Form.Item
|
||||||
|
label='Select cloud storage'
|
||||||
|
name='cloudStorageSelect'
|
||||||
|
rules={[{ required: true, message: 'Please, specify a cloud storage' }]}
|
||||||
|
valuePropName='label'
|
||||||
|
>
|
||||||
|
<AutoComplete
|
||||||
|
onBlur={onBlur}
|
||||||
|
value={searchPhrase}
|
||||||
|
placeholder='Search...'
|
||||||
|
showSearch
|
||||||
|
onSearch={(phrase: string) => {
|
||||||
|
setSearchPhrase(phrase);
|
||||||
|
}}
|
||||||
|
options={list.map((_cloudStorage) => ({
|
||||||
|
value: _cloudStorage.id.toString(),
|
||||||
|
label: (
|
||||||
|
<span
|
||||||
|
className='cvat-cloud-storage-select-provider'
|
||||||
|
>
|
||||||
|
{_cloudStorage.providerType === ProviderType.AWS_S3_BUCKET ? (
|
||||||
|
<S3Provider />
|
||||||
|
) : (
|
||||||
|
<AzureProvider />
|
||||||
|
)}
|
||||||
|
{_cloudStorage.displayName}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
|
onSelect={(value: string) => {
|
||||||
|
const selectedCloudStorage =
|
||||||
|
list.filter((_cloudStorage: CloudStorage) => _cloudStorage.id === +value)[0] || null;
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
selectedCloudStorage.manifestPath = selectedCloudStorage.manifests[0];
|
||||||
|
onSelectCloudStorage(selectedCloudStorage);
|
||||||
|
setSearchPhrase(selectedCloudStorage?.displayName || '');
|
||||||
|
}}
|
||||||
|
allowClear
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</AutoComplete>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{cloudStorage ? (
|
||||||
|
<Form.Item
|
||||||
|
label='Select manifest file'
|
||||||
|
name='manifestSelect'
|
||||||
|
rules={[{ required: true, message: 'Please, specify a manifest file' }]}
|
||||||
|
initialValue={cloudStorage.manifests[0]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
onSelect={(value: string) => setSelectedManifest(value)}
|
||||||
|
>
|
||||||
|
{cloudStorage.manifests.map(
|
||||||
|
(manifest: string): JSX.Element => (
|
||||||
|
<Option key={manifest} value={manifest}>
|
||||||
|
{manifest}
|
||||||
|
</Option>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{cloudStorage && selectedManifest ? (
|
||||||
|
<Form.Item
|
||||||
|
label='Files'
|
||||||
|
name='cloudStorageFiles'
|
||||||
|
rules={[{ required: true, message: 'Please, select a files' }]}
|
||||||
|
>
|
||||||
|
<CloudStorageFiles
|
||||||
|
cloudStorage={cloudStorage}
|
||||||
|
selectedManifest={selectedManifest}
|
||||||
|
selectedFiles={selectedFiles}
|
||||||
|
onSelectFiles={onSelectFiles}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : null}
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,87 +0,0 @@
|
|||||||
// Copyright (C) 2020-2021 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
import './styles.scss';
|
|
||||||
import React from 'react';
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
import Search from 'antd/lib/input/Search';
|
|
||||||
|
|
||||||
import SearchTooltip from 'components/search-tooltip/search-tooltip';
|
|
||||||
import { CombinedState, ProjectsQuery } from 'reducers/interfaces';
|
|
||||||
import { getProjectsAsync } from 'actions/projects-actions';
|
|
||||||
|
|
||||||
function getSearchField(gettingQuery: ProjectsQuery): string {
|
|
||||||
let searchString = '';
|
|
||||||
for (const field of Object.keys(gettingQuery)) {
|
|
||||||
if (gettingQuery[field] !== null && field !== 'page') {
|
|
||||||
if (field === 'search') {
|
|
||||||
return (gettingQuery[field] as any) as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not constant condition
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (typeof (gettingQuery[field] === 'number')) {
|
|
||||||
searchString += `${field}:${gettingQuery[field]} AND `;
|
|
||||||
} else {
|
|
||||||
searchString += `${field}:"${gettingQuery[field]}" AND `;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return searchString.slice(0, -5);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ProjectSearchField(): JSX.Element {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const gettingQuery = useSelector((state: CombinedState) => state.projects.gettingQuery);
|
|
||||||
|
|
||||||
const handleSearch = (value: string): void => {
|
|
||||||
const query = { ...gettingQuery };
|
|
||||||
const search = value
|
|
||||||
.replace(/\s+/g, ' ')
|
|
||||||
.replace(/\s*:+\s*/g, ':')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const fields = Object.keys(query).filter((key) => key !== 'page');
|
|
||||||
for (const field of fields) {
|
|
||||||
query[field] = null;
|
|
||||||
}
|
|
||||||
query.search = null;
|
|
||||||
|
|
||||||
let specificRequest = false;
|
|
||||||
for (const param of search.split(/[\s]+and[\s]+|[\s]+AND[\s]+/)) {
|
|
||||||
if (param.includes(':')) {
|
|
||||||
const [field, fieldValue] = param.split(':');
|
|
||||||
if (fields.includes(field) && !!fieldValue) {
|
|
||||||
specificRequest = true;
|
|
||||||
if (field === 'id') {
|
|
||||||
if (Number.isInteger(+fieldValue)) {
|
|
||||||
query[field] = +fieldValue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query[field] = fieldValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query.page = 1;
|
|
||||||
if (!specificRequest && value) {
|
|
||||||
query.search = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(getProjectsAsync(query));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SearchTooltip instance='project'>
|
|
||||||
<Search
|
|
||||||
defaultValue={getSearchField(gettingQuery)}
|
|
||||||
onSearch={handleSearch}
|
|
||||||
size='large'
|
|
||||||
placeholder='Search'
|
|
||||||
/>
|
|
||||||
</SearchTooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React from 'react';
|
||||||
|
import Search from 'antd/lib/input/Search';
|
||||||
|
import SearchTooltip from 'components/search-tooltip/search-tooltip';
|
||||||
|
|
||||||
|
interface Query {
|
||||||
|
[key: string]: string | number | boolean | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
query: Query;
|
||||||
|
instance: 'task' | 'project' | 'cloudstorage';
|
||||||
|
onSearch(query: object): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SearchField(props: Props): JSX.Element {
|
||||||
|
const { onSearch, query, instance } = props;
|
||||||
|
function parse(_query: Query): string {
|
||||||
|
let searchString = '';
|
||||||
|
for (const field of Object.keys(_query)) {
|
||||||
|
const value = _query[field];
|
||||||
|
if (value !== null && typeof value !== 'undefined' && field !== 'page') {
|
||||||
|
if (field === 'search') {
|
||||||
|
return _query[field] as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (typeof (_query[field] === 'number')) {
|
||||||
|
searchString += `${field}: ${_query[field]} AND `;
|
||||||
|
} else {
|
||||||
|
searchString += `${field}: "${_query[field]}" AND `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchString.slice(0, -5);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = (value: string): void => {
|
||||||
|
const currentQuery = { ...query };
|
||||||
|
const search = value
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.replace(/\s*:+\s*/g, ':')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const fields = Object.keys(query).filter((key) => key !== 'page');
|
||||||
|
for (const field of fields) {
|
||||||
|
currentQuery[field] = null;
|
||||||
|
}
|
||||||
|
currentQuery.search = null;
|
||||||
|
|
||||||
|
let specificRequest = false;
|
||||||
|
for (const param of search.split(/[\s]+and[\s]+|[\s]+AND[\s]+/)) {
|
||||||
|
if (param.includes(':')) {
|
||||||
|
const [field, fieldValue] = param.split(':');
|
||||||
|
if (fields.includes(field) && !!fieldValue) {
|
||||||
|
specificRequest = true;
|
||||||
|
if (field === 'id') {
|
||||||
|
if (Number.isInteger(+fieldValue)) {
|
||||||
|
currentQuery[field] = +fieldValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentQuery[field] = fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentQuery.page = 1;
|
||||||
|
if (!specificRequest && value) {
|
||||||
|
currentQuery.search = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearch(currentQuery);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SearchTooltip instance={instance}>
|
||||||
|
<Search
|
||||||
|
className='cvat-search-field'
|
||||||
|
defaultValue={parse(query)}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
size='large'
|
||||||
|
placeholder='Search'
|
||||||
|
/>
|
||||||
|
</SearchTooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
@import '../../base.scss';
|
||||||
|
|
||||||
|
.cvat-search-field {
|
||||||
|
width: $grid-unit-size * 30;
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
@import '../../base.scss';
|
||||||
|
|
||||||
|
.cvat-update-cloud-storage-form-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: $grid-unit-size * 5;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 90%;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> div > span {
|
||||||
|
font-size: $grid-unit-size * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { Row, Col } from 'antd/lib/grid';
|
||||||
|
import Spin from 'antd/lib/spin';
|
||||||
|
import Result from 'antd/lib/result';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
import { getCloudStoragesAsync } from 'actions/cloud-storage-actions';
|
||||||
|
import CreateCloudStorageForm from 'components/create-cloud-storage-page/cloud-storage-form';
|
||||||
|
|
||||||
|
interface ParamType {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UpdateCloudStoragePageComponent(): JSX.Element {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const cloudStorageId = +useParams<ParamType>().id;
|
||||||
|
const isFetching = useSelector((state: CombinedState) => state.cloudStorages.fetching);
|
||||||
|
const isInitialized = useSelector((state: CombinedState) => state.cloudStorages.initialized);
|
||||||
|
const cloudStorages = useSelector((state: CombinedState) => state.cloudStorages.current)
|
||||||
|
.map((cloudStrage) => cloudStrage.instance);
|
||||||
|
const [cloudStorage] = cloudStorages.filter((_cloudStorage) => _cloudStorage.id === cloudStorageId);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!cloudStorage && !isFetching) {
|
||||||
|
dispatch(getCloudStoragesAsync({ id: cloudStorageId }));
|
||||||
|
}
|
||||||
|
}, [isFetching]);
|
||||||
|
|
||||||
|
if (!cloudStorage && !isInitialized) {
|
||||||
|
return <Spin size='large' className='cvat-spinner' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cloudStorage) {
|
||||||
|
return (
|
||||||
|
<Result
|
||||||
|
className='cvat-not-found'
|
||||||
|
status='404'
|
||||||
|
title={`Sorry, but the cloud storage #${cloudStorageId} was not found`}
|
||||||
|
subTitle='Please, be sure id you requested exists and you have appropriate permissions'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row justify='center' align='top' className='cvat-update-cloud-storage-form-wrapper'>
|
||||||
|
<Col md={20} lg={16} xl={14} xxl={9}>
|
||||||
|
<Text className='cvat-title'>{`Update cloud storage #${cloudStorageId}`}</Text>
|
||||||
|
<CreateCloudStorageForm cloudStorage={cloudStorage} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,343 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { CloudStorageActions, CloudStorageActionTypes } from 'actions/cloud-storage-actions';
|
||||||
|
import { AuthActions, AuthActionTypes } from 'actions/auth-actions';
|
||||||
|
import { CloudStoragesState, CloudStorage } from './interfaces';
|
||||||
|
|
||||||
|
const defaultState: CloudStoragesState = {
|
||||||
|
initialized: false,
|
||||||
|
fetching: false,
|
||||||
|
count: 0,
|
||||||
|
current: [],
|
||||||
|
// currentStatuses: [],
|
||||||
|
gettingQuery: {
|
||||||
|
page: 1,
|
||||||
|
id: null,
|
||||||
|
search: null,
|
||||||
|
owner: null,
|
||||||
|
displayName: null,
|
||||||
|
description: null,
|
||||||
|
resourceName: null,
|
||||||
|
providerType: null,
|
||||||
|
credentialsType: null,
|
||||||
|
status: null,
|
||||||
|
},
|
||||||
|
activities: {
|
||||||
|
creates: {
|
||||||
|
attaching: false,
|
||||||
|
id: null,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
updates: {
|
||||||
|
updating: false,
|
||||||
|
cloudStorageID: null,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
deletes: {},
|
||||||
|
contentLoads: {
|
||||||
|
cloudStorageID: null,
|
||||||
|
content: null,
|
||||||
|
fetching: false,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
// getsStatus: {
|
||||||
|
// cloudStorageID: null,
|
||||||
|
// status: null,
|
||||||
|
// fetching: false,
|
||||||
|
// error: '',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (
|
||||||
|
state: CloudStoragesState = defaultState,
|
||||||
|
action: CloudStorageActions | AuthActions,
|
||||||
|
): CloudStoragesState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case CloudStorageActionTypes.UPDATE_CLOUD_STORAGES_GETTING_QUERY:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
gettingQuery: {
|
||||||
|
...defaultState.gettingQuery,
|
||||||
|
...action.payload.query,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case CloudStorageActionTypes.GET_CLOUD_STORAGES:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
initialized: false,
|
||||||
|
fetching: true,
|
||||||
|
count: 0,
|
||||||
|
current: [],
|
||||||
|
// currentStatuses: [],
|
||||||
|
};
|
||||||
|
case CloudStorageActionTypes.GET_CLOUD_STORAGE_SUCCESS: {
|
||||||
|
const { count, query } = action.payload;
|
||||||
|
|
||||||
|
const combined = action.payload.array.map(
|
||||||
|
(cloudStorage: any, index: number): CloudStorage => ({
|
||||||
|
instance: cloudStorage,
|
||||||
|
preview: action.payload.previews[index],
|
||||||
|
status: action.payload.statuses[index],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
initialized: true,
|
||||||
|
fetching: false,
|
||||||
|
count,
|
||||||
|
gettingQuery: {
|
||||||
|
...defaultState.gettingQuery,
|
||||||
|
...query,
|
||||||
|
},
|
||||||
|
current: combined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.GET_CLOUD_STORAGE_FAILED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
initialized: true,
|
||||||
|
fetching: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.CREATE_CLOUD_STORAGE: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
creates: {
|
||||||
|
attaching: true,
|
||||||
|
id: null,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.CREATE_CLOUD_STORAGE_SUCCESS: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
creates: {
|
||||||
|
attaching: false,
|
||||||
|
id: action.payload.cloudStorageID,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.CREATE_CLOUD_STORAGE_FAILED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
creates: {
|
||||||
|
...state.activities.creates,
|
||||||
|
attaching: false,
|
||||||
|
error: action.payload.error.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.UPDATE_CLOUD_STORAGE: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
updates: {
|
||||||
|
updating: true,
|
||||||
|
cloudStorageID: null,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.UPDATE_CLOUD_STORAGE_SUCCESS: {
|
||||||
|
const { cloudStorage } = action.payload;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
updates: {
|
||||||
|
updating: false,
|
||||||
|
cloudStorageID: cloudStorage.id,
|
||||||
|
error: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
current: state.current.map(
|
||||||
|
(_cloudStorage: CloudStorage): CloudStorage => {
|
||||||
|
if (_cloudStorage.id === cloudStorage.id) {
|
||||||
|
return cloudStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cloudStorage;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.UPDATE_CLOUD_STORAGE_FAILED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
updates: {
|
||||||
|
...state.activities.updates,
|
||||||
|
updating: false,
|
||||||
|
error: action.payload.error.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.DELETE_CLOUD_STORAGE: {
|
||||||
|
const { cloudStorageID } = action.payload;
|
||||||
|
const { deletes } = state.activities;
|
||||||
|
|
||||||
|
deletes[cloudStorageID] = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
deletes: {
|
||||||
|
...deletes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.DELETE_CLOUD_STORAGE_SUCCESS: {
|
||||||
|
const { cloudStorageID } = action.payload;
|
||||||
|
const { deletes } = state.activities;
|
||||||
|
|
||||||
|
deletes[cloudStorageID] = true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
deletes: {
|
||||||
|
...deletes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.DELETE_CLOUD_STORAGE_FAILED: {
|
||||||
|
const { cloudStorageID } = action.payload;
|
||||||
|
const { deletes } = state.activities;
|
||||||
|
|
||||||
|
delete deletes[cloudStorageID];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
deletes: {
|
||||||
|
...deletes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
contentLoads: {
|
||||||
|
cloudStorageID: null,
|
||||||
|
content: null,
|
||||||
|
error: '',
|
||||||
|
fetching: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT_SUCCESS: {
|
||||||
|
const { cloudStorageID, content } = action.payload;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
contentLoads: {
|
||||||
|
cloudStorageID,
|
||||||
|
content,
|
||||||
|
error: '',
|
||||||
|
fetching: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT_FAILED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activities: {
|
||||||
|
...state.activities,
|
||||||
|
contentLoads: {
|
||||||
|
...state.activities.contentLoads,
|
||||||
|
error: action.payload.error.toString(),
|
||||||
|
fetching: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS:
|
||||||
|
// return {
|
||||||
|
// ...state,
|
||||||
|
// activities: {
|
||||||
|
// ...state.activities,
|
||||||
|
// getsStatus: {
|
||||||
|
// cloudStorageID: null,
|
||||||
|
// status: null,
|
||||||
|
// error: '',
|
||||||
|
// fetching: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_SUCCESS: {
|
||||||
|
// const { cloudStorageID, status } = action.payload;
|
||||||
|
// const statuses = state.currentStatuses;
|
||||||
|
// const index = statuses.findIndex((item) => item.id === cloudStorageID);
|
||||||
|
// if (index !== -1) {
|
||||||
|
// statuses[index] = {
|
||||||
|
// ...statuses[index],
|
||||||
|
// status,
|
||||||
|
// };
|
||||||
|
// } else {
|
||||||
|
// statuses.push({
|
||||||
|
// id: cloudStorageID,
|
||||||
|
// status,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// return {
|
||||||
|
// ...state,
|
||||||
|
// currentStatuses: statuses,
|
||||||
|
// activities: {
|
||||||
|
// ...state.activities,
|
||||||
|
// getsStatus: {
|
||||||
|
// cloudStorageID,
|
||||||
|
// status,
|
||||||
|
// error: '',
|
||||||
|
// fetching: false,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_FAILED: {
|
||||||
|
// return {
|
||||||
|
// ...state,
|
||||||
|
// activities: {
|
||||||
|
// ...state.activities,
|
||||||
|
// getsStatus: {
|
||||||
|
// ...state.activities.getsStatus,
|
||||||
|
// error: action.payload.error.toString(),
|
||||||
|
// fetching: false,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
case AuthActionTypes.LOGOUT_SUCCESS: {
|
||||||
|
return { ...defaultState };
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
export enum ProviderType {
|
||||||
|
AWS_S3_BUCKET = 'AWS_S3_BUCKET',
|
||||||
|
AZURE_CONTAINER = 'AZURE_CONTAINER',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CredentialsType {
|
||||||
|
KEY_SECRET_KEY_PAIR = 'KEY_SECRET_KEY_PAIR',
|
||||||
|
ACCOUNT_NAME_TOKEN_PAIR = 'ACCOUNT_NAME_TOKEN_PAIR',
|
||||||
|
ANONYMOUS_ACCESS = 'ANONYMOUS_ACCESS',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum StorageStatuses {
|
||||||
|
AVAILABLE = 'AVAILABLE',
|
||||||
|
FORBIDDEN = 'FORBIDDEN',
|
||||||
|
NOT_FOUND = 'NOT_FOUND',
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue