React & Antd UI: Model manager (#856)

* Supported git to create and sync
* Updated antd
* Updated icons
* Improved header
* Top bar for models & empty models list
* Removed one extra reducer and actions
* Removed one extra reducer and actions
* Crossplatform css
* Models reducers, some models actions, base for model list, imrovements
* Models list, ability to delete models
* Added ability to upload models
* Improved form, reinit models after create
* Removed some importants in css
* Model running dialog window, a lot of fixes
main
Boris Sekachev 6 years ago committed by Nikita Manovich
parent 73e5327bad
commit 9f63686baf

@ -89,6 +89,11 @@
return result;
};
cvat.server.request.implementation = async (url, data) => {
const result = await serverProxy.server.request(url, data);
return result;
};
cvat.users.get.implementation = async (filter) => {
checkFilter(filter, {
self: isBoolean,

@ -177,6 +177,22 @@ function build() {
.apiWrapper(cvat.server.authorized);
return result;
},
/**
* Method allows to do requests via cvat-core with authorization headers
* @method request
* @async
* @memberof module:API.cvat.server
* @param {string} url
* @param {Object} data request parameters: method, headers, data, etc.
* @returns {Object | undefined} response data if exist
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async request(url, data) {
const result = await PluginRegistry
.apiWrapper(cvat.server.request, url, data);
return result;
},
},
/**
* Namespace is used for getting tasks

@ -182,6 +182,17 @@
return true;
}
async function serverRequest(url, data) {
try {
return (await Axios({
url,
...data,
})).data;
} catch (errorData) {
throw generateError(errorData, 'Could not have done the request');
}
}
async function getTasks(filter = '') {
const { backendAPI } = config;
@ -560,6 +571,7 @@
logout,
authorized,
register,
request: serverRequest,
}),
writable: false,
},

@ -1390,9 +1390,9 @@
}
},
"antd": {
"version": "3.24.3",
"resolved": "https://registry.npmjs.org/antd/-/antd-3.24.3.tgz",
"integrity": "sha512-yr4kV8lUdYNCOj5+TjIufLGYF0naTfNiAJV0JWqh9RzRGapGVES928K/2gyF7Ow3da6ZlsgxtO1P25exbvDlSw==",
"version": "3.25.2",
"resolved": "https://registry.npmjs.org/antd/-/antd-3.25.2.tgz",
"integrity": "sha512-+qF1bgU7rUkPIkggIIV0fmm+9pPacl50BBd6NNUR2+kKJOFYjwrnP39ZqJRsYNy5bX9VgR454fz9KEuW7HPjog==",
"requires": {
"@ant-design/create-react-context": "^0.2.4",
"@ant-design/icons": "~2.1.1",
@ -1410,7 +1410,7 @@
"omit.js": "^1.0.2",
"prop-types": "^15.7.2",
"raf": "^3.4.1",
"rc-animate": "^2.8.3",
"rc-animate": "^2.10.2",
"rc-calendar": "~9.15.5",
"rc-cascader": "~0.17.4",
"rc-checkbox": "~2.1.6",
@ -1419,7 +1419,7 @@
"rc-drawer": "~3.0.0",
"rc-dropdown": "~2.4.1",
"rc-editor-mention": "^1.1.13",
"rc-form": "^2.4.5",
"rc-form": "^2.4.10",
"rc-input-number": "~4.5.0",
"rc-mentions": "~0.4.0",
"rc-menu": "~7.5.1",
@ -1439,7 +1439,7 @@
"rc-tree": "~2.1.0",
"rc-tree-select": "~2.9.1",
"rc-trigger": "^2.6.2",
"rc-upload": "~2.8.0",
"rc-upload": "~2.9.1",
"rc-util": "^4.10.0",
"react-lazy-load": "^3.0.13",
"react-lifecycles-compat": "^3.0.4",
@ -5862,36 +5862,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash._getnative": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
"integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U="
},
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
"lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo="
},
"lodash.isarray": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U="
},
"lodash.keys": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
"integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
"requires": {
"lodash._getnative": "^3.0.0",
"lodash.isarguments": "^3.0.0",
"lodash.isarray": "^3.0.0"
}
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
@ -7390,23 +7365,23 @@
}
},
"rc-animate": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.1.tgz",
"integrity": "sha512-yfP3g5fNf8wB5eh85nim2IGrqNu5u7TKrrSh710+1vlUqZvnI2R5YHK99IBCQNgkLCAWjT0sHtkcYdynjly39w==",
"version": "2.10.2",
"resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.2.tgz",
"integrity": "sha512-cE/A7piAzoWFSgUD69NmmMraqCeqVBa51UErod8NS3LUEqWfppSVagHfa0qHAlwPVPiIBg3emRONyny3eiH0Dg==",
"requires": {
"babel-runtime": "6.x",
"classnames": "^2.2.6",
"css-animation": "^1.3.2",
"prop-types": "15.x",
"raf": "^3.4.0",
"rc-util": "^4.8.0",
"rc-util": "^4.15.3",
"react-lifecycles-compat": "^3.0.4"
}
},
"rc-calendar": {
"version": "9.15.6",
"resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.6.tgz",
"integrity": "sha512-TJD4cUXsBAjyCzo7BaGb86nZyJetBUt/Rpu0H1WWhp9AJc+Tl7aj7TCD3TM5Y8Ak/yxsA8WDBMuKw1XdQMsM5g==",
"version": "9.15.8",
"resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.8.tgz",
"integrity": "sha512-x3zVaZSRX7FkRNKw7nz3tutwrlIrU1aqMn5GtRUmlf84GnXLtd9fuuydxeNkFWfcHry3BPSto7+r9TK2al0h+g==",
"requires": {
"babel-runtime": "6.x",
"classnames": "2.x",
@ -7457,9 +7432,9 @@
}
},
"rc-dialog": {
"version": "7.5.12",
"resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.5.12.tgz",
"integrity": "sha512-FsZQfHBXYjBwuxUN9Cd+0n7YRSyemDtRJ9jX2a1HvIf4ajBJK9WVUiWm2+K1vZBZOciA+jm6gQETqyXzDKnwzQ==",
"version": "7.5.13",
"resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.5.13.tgz",
"integrity": "sha512-tmubIipW/qoCmRlHHV8tpepDaFhuhk+SeSFSyRhNKW4mYgflsEYQmYWilyCJHy6UzKl84bSyFvJskhc1z1Hniw==",
"requires": {
"babel-runtime": "6.x",
"rc-animate": "2.x",
@ -7519,9 +7494,9 @@
}
},
"rc-form": {
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.9.tgz",
"integrity": "sha512-uu6wtJqSQWTFOgv1NcYJIPf7TlJHmQHbDJTBQQuQsKKap8CiW6aeAfvOZpThQuWwV/NeznP4WKeOJurIw4zzlA==",
"version": "2.4.10",
"resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.10.tgz",
"integrity": "sha512-h6a5Nvn6fMe3BfLpIWwL2RUkfXs1tvtifblTgGgH0UfzGgiQ5M12jiMJaAXek7TDDBUw90/c5vlZ6wFZjW0IgQ==",
"requires": {
"async-validator": "~1.11.3",
"babel-runtime": "6.x",
@ -7529,6 +7504,7 @@
"dom-scroll-into-view": "1.x",
"hoist-non-react-statics": "^3.3.0",
"lodash": "^4.17.4",
"rc-util": "^4.15.3",
"warning": "^4.0.3"
}
},
@ -7596,9 +7572,9 @@
}
},
"rc-pagination": {
"version": "1.20.9",
"resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.9.tgz",
"integrity": "sha512-X/y2kZWrUyX/x7Ncbh/KrcPxStMuQTytqx4XPsla5ub881wGpiCdiVJxfhlqlVlqJmXRsxLYAcn8Vbi8pmmjKA==",
"version": "1.20.11",
"resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.11.tgz",
"integrity": "sha512-2wKO5kO+ELx1/zlqTY8TwGBruzofi+1BcZ7Z4xalMlLbDMTuUU4FDljbBBP/n9D2llK+NtgWA619PMBhInozZw==",
"requires": {
"babel-runtime": "6.x",
"classnames": "^2.2.6",
@ -7851,9 +7827,9 @@
}
},
"rc-upload": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.8.1.tgz",
"integrity": "sha512-FmKZgWsgOyKeZuperDjHrj8Qx5fdQqYuNpmDR50AP7Za87o8QsRvCbIKG2pgQ9MpNkUbiQOV15FqlQBl2WisfQ==",
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.9.2.tgz",
"integrity": "sha512-USjuWpTRJl3my32G5woysTaGrAld+S4dvvZ9kW6RX/RkekfmLDjvWc5ho8Mj/+6B6/tDRJnyGyvMxMQNkW7cvw==",
"requires": {
"babel-runtime": "6.x",
"classnames": "^2.2.5",
@ -7862,25 +7838,15 @@
}
},
"rc-util": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.14.4.tgz",
"integrity": "sha512-GQgEn6ywJYZq1NEoZ6NZzeaE2U6mT6DhdqrtRV5IBNM3yTZZW8HRjIiMOpXOhTEUj10bnHnKWKZpC36RoNmS9Q==",
"version": "4.15.6",
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.15.6.tgz",
"integrity": "sha512-W6HB1gIn+xZLxmQfLkhMnAtaZY9RktcOH2I0Tbam4D4ZDFrkO33f3M7IolN0EPtLMpf4Mv/dEQNclY77/PtBpg==",
"requires": {
"add-dom-event-listener": "^1.1.0",
"babel-runtime": "6.x",
"prop-types": "^15.5.10",
"react-lifecycles-compat": "^3.0.4",
"shallowequal": "^0.2.2"
},
"dependencies": {
"shallowequal": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz",
"integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=",
"requires": {
"lodash.keys": "^3.1.2"
}
}
"shallowequal": "^1.1.0"
}
},
"react": {

@ -39,7 +39,7 @@
"@types/react-router": "^5.0.5",
"@types/react-router-dom": "^5.1.0",
"@types/react-share": "^3.0.1",
"antd": "^3.24.2",
"antd": "^3.25.2",
"dotenv-webpack": "^1.7.0",
"moment": "^2.24.0",
"prop-types": "^15.7.2",

@ -0,0 +1,417 @@
import { AnyAction, Dispatch, ActionCreator } from 'redux';
import { ThunkAction } from 'redux-thunk';
import getCore from '../core';
import { getCVATStore } from '../store';
import { Model, ModelFiles, CombinedState } from '../reducers/interfaces';
export enum ModelsActionTypes {
GET_MODELS = 'GET_MODELS',
GET_MODELS_SUCCESS = 'GET_MODELS_SUCCESS',
GET_MODELS_FAILED = 'GET_MODELS_FAILED',
DELETE_MODEL = 'DELETE_MODEL',
DELETE_MODEL_SUCCESS = 'DELETE_MODEL_SUCCESS',
DELETE_MODEL_FAILED = 'DELETE_MODEL_FAILED',
CREATE_MODEL = 'CREATE_MODEL',
CREATE_MODEL_SUCCESS = 'CREATE_MODEL_SUCCESS',
CREATE_MODEL_FAILED = 'CREATE_MODEL_FAILED',
CREATE_MODEL_STATUS_UPDATED = 'CREATE_MODEL_STATUS_UPDATED',
INFER_MODEL = 'INFER_MODEL',
INFER_MODEL_SUCCESS = 'INFER_MODEL_SUCCESS',
INFER_MODEL_FAILED = 'INFER_MODEL_FAILED',
GET_INFERENCE_STATUS = 'GET_INFERENCE_STATUS',
GET_INFERENCE_STATUS_SUCCESS = 'GET_INFERENCE_STATUS_SUCCESS',
GET_INFERENCE_STATUS_FAILED = 'GET_INFERENCE_STATUS_FAILED',
SHOW_RUN_MODEL_DIALOG = 'SHOW_RUN_MODEL_DIALOG',
CLOSE_RUN_MODEL_DIALOG = 'CLOSE_RUN_MODEL_DIALOG',
}
export enum PreinstalledModels {
RCNN = 'RCNN Object Detector',
MaskRCNN = 'Mask RCNN Object Detector',
}
const core = getCore();
const baseURL = core.config.backendAPI.slice(0, -7);
function getModels(): AnyAction {
const action = {
type: ModelsActionTypes.GET_MODELS,
payload: {},
};
return action;
}
function getModelsSuccess(models: Model[]): AnyAction {
const action = {
type: ModelsActionTypes.GET_MODELS_SUCCESS,
payload: {
models,
},
};
return action;
}
function getModelsFailed(error: any): AnyAction {
const action = {
type: ModelsActionTypes.GET_MODELS_FAILED,
payload: {
error,
},
};
return action;
}
export function getModelsAsync():
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const store = getCVATStore();
const state: CombinedState = store.getState();
const OpenVINO = state.plugins.plugins.AUTO_ANNOTATION;
const RCNN = state.plugins.plugins.TF_ANNOTATION;
const MaskRCNN = state.plugins.plugins.TF_SEGMENTATION;
dispatch(getModels());
const models: Model[] = [];
try {
if (OpenVINO) {
const response = await core.server.request(
`${baseURL}/auto_annotation/meta/get`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: JSON.stringify([]),
},
);
for (const model of response.models) {
models.push({
id: model.id,
ownerID: model.owner,
primary: model.primary,
name: model.name,
uploadDate: model.uploadDate,
updateDate: model.updateDate,
labels: [...model.labels],
});
}
}
if (RCNN) {
models.push({
id: null,
ownerID: null,
primary: true,
name: PreinstalledModels.RCNN,
uploadDate: '',
updateDate: '',
labels: ['surfboard', 'car', 'skateboard', 'boat', 'clock',
'cat', 'cow', 'knife', 'apple', 'cup', 'tv',
'baseball_bat', 'book', 'suitcase', 'tennis_racket',
'stop_sign', 'couch', 'cell_phone', 'keyboard',
'cake', 'tie', 'frisbee', 'truck', 'fire_hydrant',
'snowboard', 'bed', 'vase', 'teddy_bear',
'toaster', 'wine_glass', 'traffic_light',
'broccoli', 'backpack', 'carrot', 'potted_plant',
'donut', 'umbrella', 'parking_meter', 'bottle',
'sandwich', 'motorcycle', 'bear', 'banana',
'person', 'scissors', 'elephant', 'dining_table',
'toothbrush', 'toilet', 'skis', 'bowl', 'sheep',
'refrigerator', 'oven', 'microwave', 'train',
'orange', 'mouse', 'laptop', 'bench', 'bicycle',
'fork', 'kite', 'zebra', 'baseball_glove', 'bus',
'spoon', 'horse', 'handbag', 'pizza', 'sports_ball',
'airplane', 'hair_drier', 'hot_dog', 'remote',
'sink', 'dog', 'bird', 'giraffe', 'chair',
],
});
}
if (MaskRCNN) {
models.push({
id: null,
ownerID: null,
primary: true,
name: PreinstalledModels.MaskRCNN,
uploadDate: '',
updateDate: '',
labels: ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane',
'bus', 'train', 'truck', 'boat', 'traffic light',
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird',
'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear',
'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie',
'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
'kite', 'baseball bat', 'baseball glove', 'skateboard',
'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',
'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed',
'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
'keyboard', 'cell phone', 'microwave', 'oven', 'toaster',
'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors',
'teddy bear', 'hair drier', 'toothbrush',
],
});
}
} catch (error) {
dispatch(getModelsFailed(error));
return;
}
dispatch(getModelsSuccess(models));
};
}
function deleteModel(id: number): AnyAction {
const action = {
type: ModelsActionTypes.DELETE_MODEL,
payload: {
id,
},
};
return action;
}
function deleteModelSuccess(id: number): AnyAction {
const action = {
type: ModelsActionTypes.DELETE_MODEL_SUCCESS,
payload: {
id,
},
};
return action;
}
function deleteModelFailed(id: number, error: any): AnyAction {
const action = {
type: ModelsActionTypes.DELETE_MODEL_FAILED,
payload: {
error,
id,
},
};
return action;
}
export function deleteModelAsync(id: number): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(deleteModel(id));
try {
await core.server.request(`${baseURL}/auto_annotation/delete/${id}`, {
method: 'DELETE',
});
} catch (error) {
dispatch(deleteModelFailed(id, error));
return;
}
dispatch(deleteModelSuccess(id));
};
}
function createModel(): AnyAction {
const action = {
type: ModelsActionTypes.CREATE_MODEL,
payload: {},
};
return action;
}
function createModelSuccess(): AnyAction {
const action = {
type: ModelsActionTypes.CREATE_MODEL_SUCCESS,
payload: {},
};
return action;
}
function createModelFailed(error: any): AnyAction {
const action = {
type: ModelsActionTypes.CREATE_MODEL_FAILED,
payload: {
error,
},
};
return action;
}
function createModelUpdateStatus(status: string): AnyAction {
const action = {
type: ModelsActionTypes.CREATE_MODEL_STATUS_UPDATED,
payload: {
status,
},
};
return action;
}
export function createModelAsync(name: string, files: ModelFiles, global: boolean):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
async function checkCallback(id: string): Promise<void> {
try {
const data = await core.server.request(
`${baseURL}/auto_annotation/check/${id}`, {
method: 'GET',
},
);
switch (data.status) {
case 'failed':
dispatch(createModelFailed(
`Checking request has returned the "${data.status}" status. Message: ${data.error}`,
));
break;
case 'unknown':
dispatch(createModelFailed(
`Checking request has returned the "${data.status}" status.`,
));
break;
case 'finished':
dispatch(createModelSuccess());
break;
default:
if ('progress' in data) {
createModelUpdateStatus(data.progress);
}
setTimeout(checkCallback.bind(null, id), 1000);
}
} catch (error) {
dispatch(createModelFailed(error));
}
}
dispatch(createModel());
const data = new FormData();
data.append('name', name);
data.append('storage', typeof files.bin === 'string' ? 'shared' : 'local');
data.append('shared', global.toString());
Object.keys(files).reduce((acc, key: string): FormData => {
acc.append(key, files[key]);
return acc;
}, data);
try {
dispatch(createModelUpdateStatus('Request is beign sent..'));
const response = await core.server.request(
`${baseURL}/auto_annotation/create`, {
method: 'POST',
data,
contentType: false,
processData: false,
},
);
dispatch(createModelUpdateStatus('Request is being processed..'));
setTimeout(checkCallback.bind(null, response.id), 1000);
} catch (error) {
dispatch(createModelFailed(error));
}
};
}
function inferModel(): AnyAction {
const action = {
type: ModelsActionTypes.INFER_MODEL,
payload: {},
};
return action;
}
function inferModelSuccess(): AnyAction {
const action = {
type: ModelsActionTypes.INFER_MODEL_SUCCESS,
payload: {},
};
return action;
}
function inferModelFailed(error: any): AnyAction {
const action = {
type: ModelsActionTypes.INFER_MODEL_FAILED,
payload: {
error,
},
};
return action;
}
export function inferModelAsync(
taskInstance: any,
model: Model,
mapping: {
[index: string]: string;
},
cleanOut: boolean,
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(inferModel());
try {
if (model.name === PreinstalledModels.RCNN) {
await core.server.request(
`${baseURL}/tensorflow/annotation/create/task/${taskInstance.id}`,
);
} else if (model.name === PreinstalledModels.MaskRCNN) {
await core.server.request(
`${baseURL}/tensorflow/segmentation/create/task/${taskInstance.id}`,
);
} else {
await core.server.request(
`${baseURL}/auto_annotation/start/${model.id}/${taskInstance.id}`, {
method: 'POST',
data: JSON.stringify({
reset: cleanOut,
labels: mapping,
}),
headers: {
'Content-Type': 'application/json',
},
},
);
}
} catch (error) {
dispatch(inferModelFailed(error));
return;
}
dispatch(inferModelSuccess());
};
}
export function closeRunModelDialog(): AnyAction {
const action = {
type: ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG,
payload: {},
};
return action;
}
export function showRunModelDialog(taskInstance: any): AnyAction {
const action = {
type: ModelsActionTypes.SHOW_RUN_MODEL_DIALOG,
payload: {
taskInstance,
},
};
return action;
}

@ -1,145 +0,0 @@
import { AnyAction, Dispatch, ActionCreator } from 'redux';
import { ThunkAction } from 'redux-thunk';
import getCore from '../core';
const core = getCore();
export enum TaskActionTypes {
GET_TASK = 'GET_TASK',
GET_TASK_SUCCESS = 'GET_TASK_SUCCESS',
GET_TASK_FAILED = 'GET_TASK_FAILED',
UPDATE_TASK = 'UPDATE_TASK',
UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS',
UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED',
}
function getTask(): AnyAction {
const action = {
type: TaskActionTypes.GET_TASK,
payload: {},
};
return action;
}
function getTaskSuccess(taskInstance: any, previewImage: string): AnyAction {
const action = {
type: TaskActionTypes.GET_TASK_SUCCESS,
payload: {
taskInstance,
previewImage,
},
};
return action;
}
function getTaskFailed(error: any): AnyAction {
const action = {
type: TaskActionTypes.GET_TASK_FAILED,
payload: {
error,
},
};
return action;
}
export function getTaskAsync(tid: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
dispatch(getTask());
const taskInstance = (await core.tasks.get({ id: tid }))[0];
if (taskInstance) {
const previewImage = await taskInstance.frames.preview();
dispatch(getTaskSuccess(taskInstance, previewImage));
} else {
throw Error(`Task ${tid} wasn't found on the server`);
}
} catch (error) {
dispatch(getTaskFailed(error));
}
};
}
function updateTask(): AnyAction {
const action = {
type: TaskActionTypes.UPDATE_TASK,
payload: {},
};
return action;
}
function updateTaskSuccess(taskInstance: any): AnyAction {
const action = {
type: TaskActionTypes.UPDATE_TASK_SUCCESS,
payload: {
taskInstance,
},
};
return action;
}
function updateTaskFailed(error: any, taskInstance: any): AnyAction {
const action = {
type: TaskActionTypes.UPDATE_TASK_FAILED,
payload: {
error,
taskInstance,
},
};
return action;
}
export function updateTaskAsync(taskInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
dispatch(updateTask());
await taskInstance.save();
const [task] = await core.tasks.get({ id: taskInstance.id });
dispatch(updateTaskSuccess(task));
} catch (error) {
// try abort all changes
let task = null;
try {
[task] = await core.tasks.get({ id: taskInstance.id });
} catch (fetchError) {
dispatch(updateTaskFailed(error, taskInstance));
return;
}
dispatch(updateTaskFailed(error, task));
}
};
}
// a job is a part of a task, so for simplify we consider
// updating the job as updating a task
export function updateJobAsync(jobInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
dispatch(updateTask());
await jobInstance.save();
const [task] = await core.tasks.get({ id: jobInstance.task.id });
dispatch(updateTaskSuccess(task));
} catch (error) {
// try abort all changes
let task = null;
try {
[task] = await core.tasks.get({ id: jobInstance.task.id });
} catch (fetchError) {
dispatch(updateTaskFailed(error, jobInstance.task));
return;
}
dispatch(updateTaskFailed(error, task));
}
};
}

@ -23,6 +23,9 @@ export enum TasksActionTypes {
CREATE_TASK_STATUS_UPDATED = 'CREATE_TASK_STATUS_UPDATED',
CREATE_TASK_SUCCESS = 'CREATE_TASK_SUCCESS',
CREATE_TASK_FAILED = 'CREATE_TASK_FAILED',
UPDATE_TASK = 'UPDATE_TASK',
UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS',
UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED',
}
function getTasks(): AnyAction {
@ -333,6 +336,21 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
taskInstance.serverFiles = data.files.share;
taskInstance.remoteFiles = data.files.remote;
if (data.advanced.repository) {
const [gitPlugin] = (await cvat.plugins.list()).filter(
(plugin: any): boolean => plugin.name === 'Git',
);
if (gitPlugin) {
gitPlugin.callbacks.onStatusChange = (status: string): void => {
dispatch(createTaskUpdateStatus(status));
};
gitPlugin.data.task = taskInstance;
gitPlugin.data.repos = data.advanced.repository;
gitPlugin.data.lfs = data.advanced.lfs;
}
}
dispatch(createTask());
try {
await taskInstance.save((status: string): void => {
@ -344,3 +362,83 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}
};
}
function updateTask(): AnyAction {
const action = {
type: TasksActionTypes.UPDATE_TASK,
payload: {},
};
return action;
}
function updateTaskSuccess(taskInstance: any): AnyAction {
const action = {
type: TasksActionTypes.UPDATE_TASK_SUCCESS,
payload: {
taskInstance,
},
};
return action;
}
function updateTaskFailed(error: any, taskInstance: any): AnyAction {
const action = {
type: TasksActionTypes.UPDATE_TASK_FAILED,
payload: {
error,
taskInstance,
},
};
return action;
}
export function updateTaskAsync(taskInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
dispatch(updateTask());
await taskInstance.save();
const [task] = await cvat.tasks.get({ id: taskInstance.id });
dispatch(updateTaskSuccess(task));
} catch (error) {
// try abort all changes
let task = null;
try {
[task] = await cvat.tasks.get({ id: taskInstance.id });
} catch (fetchError) {
dispatch(updateTaskFailed(error, taskInstance));
return;
}
dispatch(updateTaskFailed(error, task));
}
};
}
// a job is a part of a task, so for simplify we consider
// updating the job as updating a task
export function updateJobAsync(jobInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
dispatch(updateTask());
await jobInstance.save();
const [task] = await cvat.tasks.get({ id: jobInstance.task.id });
dispatch(updateTaskSuccess(task));
} catch (error) {
// try abort all changes
let task = null;
try {
[task] = await cvat.tasks.get({ id: jobInstance.task.id });
} catch (fetchError) {
dispatch(updateTaskFailed(error, jobInstance.task));
return;
}
dispatch(updateTaskFailed(error, task));
}
};
}

@ -5,6 +5,7 @@ import {
Modal,
} from 'antd';
import Text from 'antd/lib/typography/Text';
import { ClickParam } from 'antd/lib/menu/index';
import LoaderItemComponent from './loader-item';
@ -18,15 +19,18 @@ interface ActionsMenuComponentProps {
loadActivity: string | null;
dumpActivities: string[] | null;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
installedAutoAnnotation: boolean;
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void;
onDumpAnnotation: (task: any, dumper: any) => void;
onDeleteTask: (task: any) => void;
onDumpAnnotation: (taskInstance: any, dumper: any) => void;
onDeleteTask: (taskInstance: any) => void;
onOpenRunWindow: (taskInstance: any) => void;
}
interface MinActionsMenuProps {
taskInstance: any;
onDeleteTask: (task: any) => void;
onOpenRunWindow: (taskInstance: any) => void;
}
export function handleMenuClick(props: MinActionsMenuProps, params: ClickParam) {
@ -38,11 +42,8 @@ export function handleMenuClick(props: MinActionsMenuProps, params: ClickParam)
case 'tracker': {
window.open(`${tracker}`, '_blank')
return;
} case 'auto': {
return;
} case 'tf': {
} case 'auto_annotation': {
props.onOpenRunWindow(taskInstance);
return;
} case 'delete': {
const taskID = taskInstance.id;
@ -63,12 +64,15 @@ export function handleMenuClick(props: MinActionsMenuProps, params: ClickParam)
export default function ActionsMenuComponent(props: ActionsMenuComponentProps) {
const tracker = props.taskInstance.bugTracker;
const renderModelRunner = props.installedAutoAnnotation ||
props.installedTFAnnotation || props.installedTFSegmentation;
return (
<Menu subMenuCloseDelay={0.15} className='cvat-task-item-menu' onClick={
<Menu selectable={false} className='cvat-actions-menu' onClick={
(params: ClickParam) => handleMenuClick(props, params)
}>
<Menu.SubMenu key='dump' title='Dump annotations'>
<Menu.SubMenu key='dump' title={<
Text>{'Dump annotations'}</Text>
}>
{
props.dumpers.map((dumper) => DumperItemComponent({
dumper,
@ -77,7 +81,9 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps) {
onDumpAnnotation: props.onDumpAnnotation,
} ))}
</Menu.SubMenu>
<Menu.SubMenu key='load' title='Upload annotations'>
<Menu.SubMenu key='load' title={
<Text>{'Upload annotations'}</Text>
}>
{
props.loaders.map((loader) => LoaderItemComponent({
loader,
@ -87,13 +93,8 @@ export default function ActionsMenuComponent(props: ActionsMenuComponentProps) {
}))
}
</Menu.SubMenu>
{tracker ? <Menu.Item key='tracker'>Open bug tracker</Menu.Item> : null}
{ props.installedTFAnnotation ?
<Menu.Item key='tf'>Run TF annotation</Menu.Item> : null
}
{ props.installedAutoAnnotation ?
<Menu.Item key='auto'>Run auto annotation</Menu.Item> : null
}
{tracker && <Menu.Item key='tracker'>Open bug tracker</Menu.Item>}
{renderModelRunner && <Menu.Item key='auto_annotation'>Automatic annotation</Menu.Item>}
<hr/>
<Menu.Item key='delete'>Delete</Menu.Item>
</Menu>

@ -31,7 +31,7 @@ export default function DumperItemComponent(props: DumperItemComponentProps) {
const pending = !!dumpingWithThisDumper;
return (
<Menu.Item className='cvat-task-item-dump-submenu-item' key={dumper.name}>
<Menu.Item className='cvat-actions-menu-dump-submenu-item' key={dumper.name}>
<Button block={true} type='link' disabled={pending}
onClick={() => {
props.onDumpAnnotation(task, dumper);
@ -40,7 +40,7 @@ export default function DumperItemComponent(props: DumperItemComponentProps) {
<Text strong={isDefaultFormat(dumper.name, mode)}>
{dumper.name}
</Text>
{pending ? <Icon type='loading'/> : null}
{pending && <Icon type='loading'/>}
</Button>
</Menu.Item>
);

@ -27,7 +27,7 @@ export default function LoaderItemComponent(props: LoaderItemComponentProps) {
const pending = !!loadingWithThisLoader;
return (
<Menu.Item className='cvat-task-item-load-submenu-item' key={loader.name}>
<Menu.Item className='cvat-actions-menu-load-submenu-item' key={loader.name}>
<Upload
accept={`.${loader.format}`}
multiple={false}
@ -44,7 +44,7 @@ export default function LoaderItemComponent(props: LoaderItemComponentProps) {
<Button block={true} type='link' disabled={!!props.loadActivity}>
<Icon type='upload'/>
<Text>{loader.name}</Text>
{pending ? <Icon type='loading'/> : null}
{pending && <Icon type='loading'/>}
</Button>
</Upload>
</Menu.Item>

@ -0,0 +1,139 @@
import React from 'react';
import {
Row,
Col,
Icon,
Alert,
Button,
Tooltip,
Modal,
message,
} from 'antd';
import CreateModelForm, {
CreateModelForm as WrappedCreateModelForm
} from './create-model-form';
import ConnectedFileManager, {
FileManagerContainer
} from '../../containers/file-manager/file-manager';
import { ModelFiles } from '../../reducers/interfaces';
interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean;
modelCreatingError: string;
modelCreatingStatus: string;
}
export default class CreateModelContent extends React.PureComponent<Props> {
private modelForm: WrappedCreateModelForm;
private fileManagerContainer: FileManagerContainer;
public constructor(props: Props) {
super(props);
this.modelForm = null as any as WrappedCreateModelForm;
this.fileManagerContainer = null as any as FileManagerContainer;
}
private handleSubmitClick = () => {
this.modelForm.submit()
.then((data) => {
const {
local,
share,
} = this.fileManagerContainer.getFiles();
const files = local.length ? local : share;
const grouppedFiles: ModelFiles = {
xml: '',
bin: '',
py: '',
json: '',
};
(files as any).reduce((acc: ModelFiles, value: File | string): ModelFiles => {
const name = typeof value === 'string' ? value : value.name;
const [extension] = name.split('.').reverse();
if (extension in acc) {
acc[extension] = value;
}
return acc;
}, grouppedFiles);
if (Object.keys(grouppedFiles)
.map((key: string) => grouppedFiles[key])
.filter((val) => !!val).length !== 4) {
Modal.error({
title: 'Could not upload a model',
content: 'Please, specify correct files',
});
} else {
this.props.createModel(data.name, grouppedFiles, data.global);
}
}).catch(() => {
Modal.error({
title: 'Could not upload a model',
content: 'Please, check input fields',
});
})
}
public componentDidUpdate(prevProps: Props) {
if (prevProps.modelCreatingStatus !== 'CREATED'
&& this.props.modelCreatingStatus === 'CREATED') {
message.success('The model has been uploaded');
this.modelForm.resetFields();
this.fileManagerContainer.reset();
}
if (!prevProps.modelCreatingError && this.props.modelCreatingError) {
Modal.error({
title: 'Could not create task',
content: this.props.modelCreatingError,
});
}
}
public render() {
const loading = !!this.props.modelCreatingStatus
&& this.props.modelCreatingStatus !== 'CREATED';
const status = this.props.modelCreatingStatus
&& this.props.modelCreatingStatus !== 'CREATED' ? this.props.modelCreatingStatus : '';
const guideLink = 'https://github.com/opencv/cvat/tree/develop/cvat/apps/auto_annotation';
return (
<Row type='flex' justify='start' align='middle' className='cvat-create-model-content'>
<Col span={24}>
<Tooltip overlay='Click to open guide'>
<Icon onClick={() => {window.open(guideLink, '_blank')}} type='question-circle'/>
</Tooltip>
</Col>
<Col span={24}>
<CreateModelForm
wrappedComponentRef={
(ref: WrappedCreateModelForm) => this.modelForm = ref
}
/>
</Col>
<Col span={24}>
<ConnectedFileManager ref={
(container: FileManagerContainer) =>
this.fileManagerContainer = container
}/>
</Col>
<Col span={18}>
{status && <Alert message={`${status}`}/>}
</Col>
<Col span={6}>
<Button
type='danger'
disabled={loading}
loading={loading}
onClick={this.handleSubmitClick}
> Submit </Button>
</Col>
</Row>
);
}
}

@ -0,0 +1,85 @@
import React from 'react';
import {
Row,
Col,
Form,
Input,
Tooltip,
Checkbox,
} from 'antd';
import { FormComponentProps } from 'antd/lib/form/Form';
import Text from 'antd/lib/typography/Text';
type Props = FormComponentProps;
export class CreateModelForm extends React.PureComponent<Props> {
public constructor(props: Props) {
super(props);
}
public submit(): Promise<{name: string, global: boolean}> {
return new Promise((resolve, reject) => {
this.props.form.validateFields((errors, values) => {
if (!errors) {
resolve({
name: values.name,
global: values.global,
});
} else {
reject(errors);
}
});
});
}
public resetFields() {
this.props.form.resetFields();
}
public render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={(e: React.FormEvent) => e.preventDefault()}>
<Row type='flex'>
<Col>
<Text type='secondary'>Name</Text>
</Col>
</Row>
<Row>
<Col span={14}>
<Form.Item>
{ getFieldDecorator('name', {
rules: [{
required: true,
message: 'Please, specify a model name',
}],
})(<Input placeholder='Model name'/>)}
</Form.Item>
</Col>
<Col span={8} offset={2}>
<Form.Item>
<Tooltip overlay='Will this model be availabe for everyone?'>
{ getFieldDecorator('global', {
initialValue: false,
valuePropName: 'checked',
})(<Checkbox>
<Text className='cvat-black-color'>
Load globally
</Text>
</Checkbox>)}
</Tooltip>
</Form.Item>
</Col>
</Row>
</Form>
);
}
}
export default Form.create()(CreateModelForm);

@ -0,0 +1,34 @@
import React from 'react';
import {
Row,
Col,
} from 'antd';
import Text from 'antd/lib/typography/Text';
import CreateModelContent from './create-model-content';
import { ModelFiles } from '../../reducers/interfaces';
interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean;
modelCreatingError: string;
modelCreatingStatus: string;
}
export default function CreateModelPageComponent(props: Props) {
return (
<Row type='flex' justify='center' align='top' className='cvat-create-model-form-wrapper'>
<Col md={20} lg={16} xl={14} xxl={9}>
<Text className='cvat-title'>{`Upload a new model`}</Text>
<CreateModelContent
isAdmin={props.isAdmin}
modelCreatingError={props.modelCreatingError}
modelCreatingStatus={props.modelCreatingStatus}
createModel={props.createModel}
/>
</Col>
</Row>
);
}

@ -33,7 +33,7 @@ type Props = FormComponentProps & {
};
class AdvancedConfigurationForm extends React.PureComponent<Props> {
public async submit() {
public submit() {
return new Promise((resolve, reject) => {
this.props.form.validateFields((error, values) => {
if (!error) {
@ -79,7 +79,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay='Defines image compression level'>
<Text className='cvat-black-color'> Image quality </Text>
<Text className='cvat-black-color'>{'Image quality'}</Text>
{this.props.form.getFieldDecorator('imageQuality', {
initialValue: 70,
rules: [{
@ -104,7 +104,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay='Defines a number of intersected frames between different segments'>
<Text className='cvat-black-color'> Overlap size </Text>
<Text className='cvat-black-color'>{'Overlap size'}</Text>
{this.props.form.getFieldDecorator('overlapSize')(
<Input size='large' type='number'/>
)}
@ -117,7 +117,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay='Defines a number of frames in a segment'>
<Text className='cvat-black-color'> Segment size </Text>
<Text className='cvat-black-color'>{'Segment size'}</Text>
{this.props.form.getFieldDecorator('segmentSize')(
<Input size='large' type='number'/>
)}
@ -129,7 +129,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderStartFrame() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Text className='cvat-black-color'> Start frame </Text>
<Text className='cvat-black-color'>{'Start frame'}</Text>
{this.props.form.getFieldDecorator('startFrame')(
<Input
size='large'
@ -145,7 +145,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderStopFrame() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Text className='cvat-black-color'> Stop frame </Text>
<Text className='cvat-black-color'>{'Stop frame'}</Text>
{this.props.form.getFieldDecorator('stopFrame')(
<Input
size='large'
@ -161,7 +161,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
private renderFrameStep() {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Text className='cvat-black-color'> Frame step </Text>
<Text className='cvat-black-color'>{'Frame step'}</Text>
{this.props.form.getFieldDecorator('frameStep')(
<Input
size='large'
@ -198,9 +198,22 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay={`Attach a git repository to store annotations.
Path is specified in square brackets`}>
<Text className='cvat-black-color'> Dataset repository URL </Text>
<Text className='cvat-black-color'>{'Dataset repository URL'}</Text>
{this.props.form.getFieldDecorator('repository', {
// TODO: Add pattern
rules: [{
validator: (_, value, callback) => {
const [url, path] = value.split(/\s+/);
if (!patterns.validateURL.pattern.test(url)) {
callback('Git URL is not a valid');
}
if (path && !patterns.validatePath.pattern.test(path)) {
callback('Git path is not a valid');
}
callback();
}
}]
})(
<Input
placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]'
@ -234,7 +247,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return (
<Form.Item style={{marginBottom: '0px'}}>
<Tooltip overlay='Attach issue tracker where the task is described'>
<Text className='cvat-black-color'> Issue tracker </Text>
<Text className='cvat-black-color'>{'Issue tracker'}</Text>
{this.props.form.getFieldDecorator('bugTracker', {
rules: [{
...patterns.validateURL,

@ -16,7 +16,7 @@ type Props = FormComponentProps & {
};
class BasicConfigurationForm extends React.PureComponent<Props> {
public async submit() {
public submit() {
return new Promise((resolve, reject) => {
this.props.form.validateFields((error, values) => {
if (!error) {
@ -39,13 +39,13 @@ class BasicConfigurationForm extends React.PureComponent<Props> {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={(e: React.FormEvent) => e.preventDefault()}>
<Text type='secondary'> Name </Text>
<Text type='secondary'>Name</Text>
<Form.Item style={{marginBottom: '0px'}}>
{ getFieldDecorator('name', {
rules: [{
required: true,
message: 'Please, specify a name',
}] // TODO: Add task name pattern
}]
})(
<Input/>
) }

@ -137,7 +137,7 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
private renderLabelsBlock() {
return (
<Col span={24}>
<Text type='secondary'> Labels </Text>
<Text type='secondary'>Labels</Text>
<LabelsEditor
labels={this.state.labels}
onSubmit={
@ -158,7 +158,7 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
<FileManagerContainer ref={
(container: any) =>
this.fileManagerContainer = container
}/>
} withRemote={true}/>
</Col>
);
}
@ -169,7 +169,7 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
<Collapse>
<Collapse.Panel
header={
<Text className='cvat-title'> Advanced configuration </Text>
<Text className='cvat-title'>{'Advanced configuration'}</Text>
} key='1'>
<AdvancedConfigurationForm
installedGit={this.props.installedGit}
@ -218,7 +218,7 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
return (
<Row type='flex' justify='start' align='middle' className='cvat-create-task-content'>
<Col span={24}>
<Text className='cvat-title'> Basic configuration </Text>
<Text className='cvat-title'>{'Basic configuration'}</Text>
</Col>
{ this.renderBasicBlock() }
@ -226,10 +226,10 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
{ this.renderFilesBlock() }
{ this.renderAdvancedBlock() }
<Col span={14}>
<Col span={18}>
{loading ? <Alert message={this.props.status}/> : null}
</Col>
<Col span={10}>
<Col span={6}>
<Button
loading={loading}
disabled={loading}

@ -3,7 +3,6 @@ import React from 'react';
import {
Row,
Col,
Modal,
} from 'antd';
import Text from 'antd/lib/typography/Text';
@ -21,7 +20,7 @@ export default function CreateTaskPage(props: Props) {
return (
<Row type='flex' justify='center' align='top' className='cvat-create-task-form-wrapper'>
<Col md={20} lg={16} xl={14} xxl={9}>
<Text className='cvat-title'> Create a new task</Text>
<Text className='cvat-title'>{'Create a new task'}</Text>
<CreateTaskContent
status={props.status}
error={props.error}

@ -12,10 +12,12 @@ import TasksPageContainer from '../containers/tasks-page/tasks-page';
import CreateTaskPageContainer from '../containers/create-task-page/create-task-page';
import TaskPageContainer from '../containers/task-page/task-page';
import ModelsPageContainer from '../containers/models-page/models-page';
import CreateModelPageContainer from '../containers/create-model-page/create-model-page';
import AnnotationPageContainer from '../containers/annotation-page/annotation-page';
import LoginPageContainer from '../containers/login-page/login-page';
import RegisterPageContainer from '../containers/register-page/register-page';
import HeaderContainer from '../containers/header/header';
import ModelRunnerModalContainer from '../containers/model-runner-dialog/model-runner-dialog';
import FeedbackComponent from './feedback';
@ -31,6 +33,9 @@ type CVATAppProps = {
gettingAuthError: string;
gettingFormatsError: string;
gettingUsersError: string;
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
user: any;
}
@ -95,7 +100,10 @@ export default class CVATApplication extends React.PureComponent<CVATAppProps> {
const readyForRender =
(this.props.userInitialized && this.props.user == null) ||
(this.props.userInitialized && this.props.formatsInitialized &&
this.props.pluginsInitialized &&this.props.usersInitialized);
this.props.pluginsInitialized && this.props.usersInitialized);
const withModels = this.props.installedAutoAnnotation
|| this.props.installedTFAnnotation || this.props.installedTFSegmentation;
if (readyForRender) {
if (this.props.user) {
@ -106,13 +114,17 @@ export default class CVATApplication extends React.PureComponent<CVATAppProps> {
<Layout.Content>
<Switch>
<Route exact path='/tasks' component={TasksPageContainer}/>
<Route exact path='/models' component={ModelsPageContainer}/>
<Route path='/tasks/create' component={CreateTaskPageContainer}/>
<Route path='/tasks/:id' component={TaskPageContainer}/>
<Route exact path='/tasks/:id' component={TaskPageContainer}/>
<Route path='/tasks/:id/jobs/:id' component={AnnotationPageContainer}/>
<Redirect to='/tasks'/>
{ withModels &&
<Route exact path='/models' component={ModelsPageContainer}/> }
{ this.props.installedAutoAnnotation &&
<Route path='/models/create' component={CreateModelPageContainer}/> }
<Redirect push to='/tasks'/>
</Switch>
<FeedbackComponent/>
<ModelRunnerModalContainer/>
</Layout.Content>
</Layout>
</BrowserRouter>

@ -24,6 +24,7 @@ interface State {
}
interface Props {
withRemote: boolean;
treeData: TreeNodeNormal[];
onLoadData: (key: string, success: () => void, failure: () => void) => void;
}
@ -135,7 +136,7 @@ export default class FileManager extends React.PureComponent<Props, State> {
});
}}>
{ renderTreeNodes(this.props.treeData) }
</Tree> : <Text className='cvat-black-color'> No data found </Text>
</Tree> : <Text className='cvat-black-color'>{'No data found'}</Text>
}
</Tabs.TabPane>
);
@ -183,13 +184,13 @@ export default class FileManager extends React.PureComponent<Props, State> {
public render() {
return (
<>
<Text type='secondary'> Select files </Text>
<Text type='secondary'>{'Select files'}</Text>
<Tabs type='card' tabBarGutter={5} onChange={(activeKey: string) => this.setState({
active: activeKey as any,
})}>
{ this.renderLocalSelector() }
{ this.renderShareSelector() }
{ this.renderRemoteSelector() }
{ this.props.withRemote ? this.renderRemoteSelector() : null }
</Tabs>
</>
);

@ -21,82 +21,92 @@ interface HeaderContainerProps {
onLogout: () => void;
installedAnalytics: boolean;
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
username: string;
logoutError: string;
}
function HeaderContainer(props: HeaderContainerProps & RouteComponentProps) {
const cvatLogo = () => (<img src='/assets/cvat-logo.svg'/>);
const userLogo = () => (<img src='/assets/icon-account.svg' />);
type Props = HeaderContainerProps & RouteComponentProps;
if (props.logoutError) {
Modal.error({
title: 'Could not logout',
content: `${props.logoutError}`,
});
}
class HeaderContainer extends React.PureComponent<Props> {
private cvatLogo: React.FunctionComponent;
private userLogo: React.FunctionComponent;
let activeTab = null;
public constructor(props: Props) {
super(props);
this.cvatLogo = () => <img src='/assets/cvat-logo.svg'/>;
this.userLogo = () => <img src='/assets/icon-account.svg'/>;
}
if (props.history.location.pathname === '/tasks') {
activeTab = 'tasks';
} else if (props.history.location.pathname === '/models') {
activeTab = 'models';
public componentDidUpdate(prevProps: Props) {
if (!prevProps.logoutError && this.props.logoutError) {
Modal.error({
title: 'Could not logout',
content: `${this.props.logoutError}`,
});
}
}
return (
<Layout.Header className='cvat-header'>
<div className='cvat-left-header'>
<Icon className='cvat-logo-icon' component={cvatLogo}/>
public render() {
const { props } = this;
const renderModels = props.installedAutoAnnotation
|| props.installedTFAnnotation
|| props.installedTFSegmentation;
return (
<Layout.Header className='cvat-header'>
<div className='cvat-left-header'>
<Icon className='cvat-logo-icon' component={this.cvatLogo}/>
<Button className='cvat-header-button' type='link' value='tasks' onClick={
() => props.history.push('/tasks')
}> Tasks </Button>
{ props.installedAutoAnnotation ?
<Button className='cvat-header-button' type='link' value='models' onClick={
() => props.history.push('/models')
}> Models </Button> : null
}
{ props.installedAnalytics ?
<Button className='cvat-header-button' type='link' value='tasks' onClick={
() => props.history.push('/tasks')
}> Tasks </Button>
{ renderModels ?
<Button className='cvat-header-button' type='link' value='models' onClick={
() => props.history.push('/models')
}> Models </Button> : null
}
{ props.installedAnalytics ?
<Button className='cvat-header-button' type='link' onClick={
() => {
const serverHost = core.config.backendAPI.slice(0, -7);
window.open(`${serverHost}/analytics/app/kibana`, '_blank');
}
}> Analytics </Button> : null
}
</div>
<div className='cvat-right-header'>
<Button className='cvat-header-button' type='link' onClick={
() => window.open('https://github.com/opencv/cvat', '_blank')
}>
<Icon type='github'/>
<Text className='cvat-black-color'>GitHub</Text>
</Button>
<Button className='cvat-header-button' type='link' onClick={
() => {
const serverHost = core.config.backendAPI.slice(0, -7);
window.open(`${serverHost}/analytics/app/kibana`, '_blank');
}
}> Analytics </Button> : null
}
</div>
<div className='cvat-right-header'>
<Button className='cvat-header-button' type='link' onClick={
() => window.open('https://github.com/opencv/cvat', '_blank')
}>
<Icon type='github'/>
<Text className='cvat-black-color'>GitHub</Text>
</Button>
<Button className='cvat-header-button' type='link' onClick={
() => {
const serverHost = core.config.backendAPI.slice(0, -7);
window.open(`${serverHost}/documentation/user_guide.html`, '_blank')
}
}> <Icon type='question-circle'/> Help </Button>
<Menu className='cvat-header-menu' subMenuCloseDelay={0.1} mode='horizontal'>
<Menu.SubMenu title={
<span>
<Icon className='cvat-header-user-icon' component={userLogo} />
() => {
const serverHost = core.config.backendAPI.slice(0, -7);
window.open(`${serverHost}/documentation/user_guide.html`, '_blank')
}
}> <Icon type='question-circle'/> Help </Button>
<Menu className='cvat-header-menu' subMenuCloseDelay={0.1} mode='horizontal'>
<Menu.SubMenu title={
<span>
<Text strong>
{props.username.length > 14 ? `${props.username.slice(0, 10)} ...` : props.username}
</Text>
<Icon className='cvat-header-menu-icon' type='caret-down'/>
<Icon className='cvat-header-user-icon' component={this.userLogo} />
<span>
<Text strong>
{props.username.length > 14 ? `${props.username.slice(0, 10)} ...` : props.username}
</Text>
<Icon className='cvat-header-menu-icon' type='caret-down'/>
</span>
</span>
</span>
}>
<Menu.Item onClick={props.onLogout}>Logout</Menu.Item>
</Menu.SubMenu>
</Menu>
</div>
</Layout.Header>
);
}>
<Menu.Item onClick={props.onLogout}>Logout</Menu.Item>
</Menu.SubMenu>
</Menu>
</div>
</Layout.Header>
);
}
}
export default withRouter(HeaderContainer);

@ -439,7 +439,7 @@ class LabelForm extends React.PureComponent<Props, State> {
{ attributeItems.length > 0 ?
<Row type='flex' justify='start' align='middle'>
<Col>
<Text> Attributes </Text>
<Text>Attributes</Text>
</Col>
</Row> : null
}

@ -204,7 +204,7 @@ export default class LabelsEditor
<Tabs.TabPane tab={
<span>
<Icon type='edit'/>
<Text> Raw </Text>
<Text>Raw</Text>
</span>
} key='1'>
<RawViewer
@ -216,7 +216,7 @@ export default class LabelsEditor
<Tabs.TabPane tab={
<span>
<Icon type='build'/>
<Text> Constructor </Text>
<Text>Constructor</Text>
</span>
} key='2'>
{

@ -0,0 +1,340 @@
import React from 'react';
import {
Row,
Col,
Tag,
Spin,
Icon,
Modal,
Select,
Tooltip,
Checkbox,
} from 'antd';
import { Model } from '../../reducers/interfaces';
interface Props {
modelsInitialized: boolean;
models: Model[];
activeProcesses: {
[index: string]: string;
};
visible: boolean;
taskInstance: any;
startingError: string;
getModels(): void;
closeDialog(): void;
runInference(
taskInstance: any,
model: Model,
mapping: {
[index: string]: string
},
cleanOut: boolean,
): void;
}
interface State {
selectedModel: string | null;
cleanOut: boolean;
mapping: {
[index: string]: string;
};
colors: {
[index: string]: string;
};
matching: {
model: string,
task: string,
};
}
const nextColor = (function *() {
const values = [
'magenta', 'green', 'geekblue',
'orange', 'red', 'cyan',
'blue', 'volcano', 'purple',
];
for (let i = 0; i < values.length; i++) {
yield values[i];
if (i === values.length) {
i = 0;
}
}
})();
export default class ModelRunnerModalComponent extends React.PureComponent<Props, State> {
public constructor(props: Props) {
super(props);
this.state = {
selectedModel: null,
mapping: {},
colors: {},
cleanOut: false,
matching: {
model: '',
task: '',
},
};
}
private renderModelSelector() {
return (
<Row type='flex' align='middle'>
<Col span={4}>Model:</Col>
<Col span={19}>
<Select
placeholder='Select a model'
style={{width: '100%'}}
onChange={(value: string) => this.setState({
selectedModel: value,
mapping: {},
})
}>
{this.props.models.map((model) =>
<Select.Option key={model.name}>
{model.name}
</Select.Option>
)}
</Select>
</Col>
</Row>
);
}
private renderMappingTag(modelLabel: string, taskLabel: string) {
return (
<Row key={`${modelLabel}-${taskLabel}`} type='flex' justify='start' align='middle'>
<Col span={10}>
<Tag color={this.state.colors[modelLabel]}>{modelLabel}</Tag>
</Col>
<Col span={10} offset={1}>
<Tag color={this.state.colors[modelLabel]}>{taskLabel}</Tag>
</Col>
<Col span={1} offset={1}>
<Tooltip overlay='Remove the mapped values'>
<Icon
className='cvat-run-model-dialog-remove-mapping-icon'
type='close-circle'
onClick={() => {
const mapping = {...this.state.mapping};
delete mapping[modelLabel];
this.setState({
mapping,
});
}}/>
</Tooltip>
</Col>
</Row>
);
}
private renderMappingInputSelector(
value: string,
current: string,
options: string[]
) {
return (
<Select
value={value}
placeholder={`${current} labels`}
style={{width: '100%'}}
onChange={(value: string) => {
const anotherValue = current === 'Model' ?
this.state.matching.task : this.state.matching.model;
if (!anotherValue) {
const matching = { ...this.state.matching };
if (current === 'Model') {
matching.model = value;
} else {
matching.task = value;
}
this.setState({
matching,
});
} else {
const colors = {...this.state.colors};
const mapping = {...this.state.mapping};
if (current === 'Model') {
colors[value] = nextColor.next().value;
mapping[value] = anotherValue;
} else {
colors[anotherValue] = nextColor.next().value;
mapping[anotherValue] = value;
}
this.setState({
colors,
mapping,
matching: {
task: '',
model: '',
},
})
}
}}
>
{options.map((label: string) =>
<Select.Option key={label}>
{label}
</Select.Option>
)}
</Select>
);
}
private renderMappingInput(availableModelLabels: string[], availableTaskLabels: string[]) {
return (
<Row type='flex' justify='start' align='middle'>
<Col span={10}>
{this.renderMappingInputSelector(
this.state.matching.model,
'Model',
availableModelLabels,
)}
</Col>
<Col span={10} offset={1}>
{this.renderMappingInputSelector(
this.state.matching.task,
'Task',
availableTaskLabels,
)}
</Col>
<Col span={1} offset={1}>
<Tooltip overlay='Specify a label mapping between model labels and task labels'>
<Icon className='cvat-run-model-dialog-info-icon' type='question-circle'/>
</Tooltip>
</Col>
</Row>
);
}
private renderContent() {
const model = this.state.selectedModel && this.props.models
.filter((model) => model.name === this.state.selectedModel)[0];
const excludedLabels: {
model: string[],
task: string[],
} = {
model: [],
task: [],
};
const withMapping = model && !model.primary;
const tags = withMapping ? Object.keys(this.state.mapping)
.map((modelLabel: string) => {
const taskLabel = this.state.mapping[modelLabel];
excludedLabels.model.push(modelLabel);
excludedLabels.task.push(taskLabel);
return this.renderMappingTag(
modelLabel,
this.state.mapping[modelLabel],
);
}) : [];
const availableModelLabels = model ? model.labels
.filter(
(label: string) => !excludedLabels.model.includes(label),
) : [];
const availableTaskLabels = this.props.taskInstance.labels
.map(
(label: any) => label.name,
).filter((label: string) => !excludedLabels.task.includes(label))
const mappingISAvailable = !!availableModelLabels.length
&& !!availableTaskLabels.length;
return (
<div className='cvat-run-model-dialog'>
{ this.renderModelSelector() }
{ withMapping && tags}
{ withMapping
&& mappingISAvailable
&& this.renderMappingInput(availableModelLabels, availableTaskLabels)
}
{ withMapping &&
<div>
<Checkbox
checked={this.state.cleanOut}
onChange={(e: any) => this.setState({
cleanOut: e.target.checked,
})}
> Clean old annotations </Checkbox>
</div>
}
</div>
);
}
private renderSpin() {
return (
<Spin size='large' style={{margin: '25% 50%'}}/>
);
}
public componentDidUpdate(prevProps: Props) {
if (!prevProps.visible && this.props.visible) {
this.setState({
selectedModel: null,
mapping: {},
matching: {
model: '',
task: '',
},
cleanOut: false,
});
}
if (!prevProps.startingError && this.props.startingError) {
Modal.error({
title: 'Could not start model inference',
content: this.props.startingError,
});
}
}
public componentDidMount() {
if (!this.props.modelsInitialized) {
this.props.getModels();
}
}
public render() {
const activeModel = this.props.models.filter(
(model) => model.name === this.state.selectedModel
)[0];
let enabledSubmit = !!activeModel
&& activeModel.primary || !!Object.keys(this.state.mapping).length;
return (
this.props.visible && <Modal
closable={false}
okType='danger'
okText='Submit'
onOk={() => {
this.props.runInference(
this.props.taskInstance,
this.props.models
.filter((model) => model.name === this.state.selectedModel)[0],
this.state.mapping,
this.state.cleanOut,
);
this.props.closeDialog()
}}
onCancel={() => this.props.closeDialog()}
okButtonProps={{disabled: !enabledSubmit}}
title='Automatic annotation'
visible={true}
>
{!this.props.modelsInitialized && this.renderSpin()}
{this.props.modelsInitialized && this.renderContent()}
</Modal>
);
}
}

@ -0,0 +1,46 @@
import React from 'react';
import {
Row,
Col,
Tag,
Select,
} from 'antd';
import Text from 'antd/lib/typography/Text';
import { Model } from '../../reducers/interfaces';
interface Props {
model: Model;
}
export default function BuiltModelItemComponent(props: Props) {
return (
<Row className='cvat-models-list-item' type='flex'>
<Col span={4} xxl={3}>
<Tag color='orange'>Tensorflow</Tag>
</Col>
<Col span={6} xxl={7}>
<Text className='cvat-black-color'>
{props.model.name}
</Text>
</Col>
<Col span={5} offset={7}>
<Select
showSearch
placeholder='Supported labels'
style={{width: '90%'}}
value='Supported labels'
>
{props.model.labels.map(
(label) => <Select.Option key={label}>
{label}
</Select.Option>)
}
</Select>
</Col>
<Col span={2}/>
</Row>
);
}

@ -0,0 +1,47 @@
import React from 'react';
import {
Row,
Col,
} from 'antd';
import Text from 'antd/lib/typography/Text';
import BuiltModelItemComponent from './built-model-item';
import { Model } from '../../reducers/interfaces';
interface Props {
models: Model[];
}
export default function IntegratedModelsListComponent(props: Props) {
const items = props.models.map((model) =>
<BuiltModelItemComponent key={model.name} model={model}/>
);
return (
<>
<Row type='flex' justify='center' align='middle'>
<Col md={22} lg={18} xl={16} xxl={14}>
<Text className='cvat-black-color' strong>Primary</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col md={22} lg={18} xl={16} xxl={14} className='cvat-models-list'>
<Row type='flex' align='middle' style={{padding: '10px'}}>
<Col span={4} xxl={3}>
<Text strong>{'Framework'}</Text>
</Col>
<Col span={6} xxl={7}>
<Text strong>{'Name'}</Text>
</Col>
<Col span={5} offset={7}>
<Text strong>Labels</Text>
</Col>
</Row>
{ items }
</Col>
</Row>
</>
);
}

@ -0,0 +1,39 @@
import React from 'react';
import { Link } from 'react-router-dom';
import Text from 'antd/lib/typography/Text';
import {
Col,
Row,
Icon,
} from 'antd';
export default function EmptyListComponent() {
const emptyTasksIcon = () => (<img src='/assets/empty-tasks-icon.svg'/>);
return (
<div className='cvat-empty-models-list'>
<Row type='flex' justify='center' align='middle'>
<Col>
<Icon className='cvat-empty-models-icon' component={emptyTasksIcon}/>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col>
<Text strong>{'No models uploaded yet ...'}</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col>
<Text type='secondary'>{'To annotate your tasks automatically'}</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col>
<Link to='/models/create'>{'upload a new model'}</Link>
</Col>
</Row>
</div>
)
}

@ -0,0 +1,54 @@
import React from 'react';
import {
Spin,
} from 'antd';
import TopBarComponent from './top-bar';
import UploadedModelsList from './uploaded-models-list';
import BuiltModelsList from './built-models-list';
import EmptyListComponent from './empty-list';
import { Model } from '../../reducers/interfaces';
interface Props {
installedAutoAnnotation: boolean;
installedTFSegmentation: boolean;
installedTFAnnotation: boolean;
modelsAreBeingFetched: boolean;
modelsFetchingError: any;
registeredUsers: any[];
models: Model[];
getModels(): void;
deleteModel(id: number): void;
}
export default function ModelsPageComponent(props: Props) {
if (props.modelsAreBeingFetched) {
props.getModels();
return (
<Spin size='large' style={{margin: '25% 45%'}}/>
);
} else {
const uploadedModels = props.models.filter((model) => model.id !== null);
const integratedModels = props.models.filter((model) => model.id === null);
return (
<div className='cvat-models-page'>
<TopBarComponent installedAutoAnnotation={props.installedAutoAnnotation}/>
{ integratedModels.length ?
<BuiltModelsList models={integratedModels}/> : null }
{ uploadedModels.length &&
<UploadedModelsList
registeredUsers={props.registeredUsers}
models={uploadedModels}
deleteModel={props.deleteModel}
/>
} { props.installedAutoAnnotation &&
!props.installedTFAnnotation &&
!props.installedTFSegmentation &&
<EmptyListComponent/>
}
</div>
);
}
}

@ -0,0 +1,39 @@
import React from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import {
Col,
Row,
Button,
} from 'antd';
import Text from 'antd/lib/typography/Text';
type Props = {
installedAutoAnnotation: boolean;
} & RouteComponentProps;
function TopBarComponent(props: Props) {
return (
<Row type='flex' justify='center' align='middle'>
<Col md={11} lg={9} xl={8} xxl={7}>
<Text className='cvat-title'>Models</Text>
</Col>
<Col
md={{span: 11}}
lg={{span: 9}}
xl={{span: 8}}
xxl={{span: 7}}
>
{ props.installedAutoAnnotation &&
<Button size='large' id='cvat-create-model-button' type='primary' onClick={
() => props.history.push('/models/create')
}> Create new model </Button>
}
</Col>
</Row>
)
}
export default withRouter(TopBarComponent);

@ -0,0 +1,76 @@
import React from 'react';
import {
Row,
Col,
Tag,
Select,
Menu,
Dropdown,
Button,
Icon,
} from 'antd';
import Text from 'antd/lib/typography/Text';
import moment from 'moment';
import { Model } from '../../reducers/interfaces';
interface Props {
model: Model;
owner: any;
onDelete(): void;
}
export default function UploadedModelItem(props: Props) {
const subMenuIcon = () => (<img src='/assets/icon-sub-menu.svg'/>);
return (
<Row className='cvat-models-list-item' type='flex'>
<Col span={4} xxl={3}>
<Tag color='purple'>OpenVINO</Tag>
</Col>
<Col span={6} xxl={7}>
<Text className='cvat-black-color'>
{props.model.name}
</Text>
</Col>
<Col span={3}>
<Text className='cvat-black-color'>
{props.owner ? props.owner.username : 'undefined'}
</Text>
</Col>
<Col span={4}>
<Text className='cvat-black-color'>
{moment(props.model.uploadDate).format('MMMM Do YYYY')}
</Text>
</Col>
<Col span={5}>
<Select
showSearch
placeholder='Supported labels'
style={{width: '90%'}}
value='Supported labels'
>
{props.model.labels.map(
(label) => <Select.Option key={label}>
{label}
</Select.Option>)
}
</Select>
</Col>
<Col span={2}>
<Text className='cvat-black-color'>Actions</Text>
<Dropdown overlay={
<Menu subMenuCloseDelay={0.15} className='cvat-task-item-menu'>
<Menu.Item onClick={() => {
props.onDelete();
}}key='delete'>Delete</Menu.Item>
</Menu>
}>
<Icon className='cvat-task-item-menu-icon' component={subMenuIcon}/>
</Dropdown>
</Col>
</Row>
);
}

@ -0,0 +1,64 @@
import React from 'react';
import {
Row,
Col,
} from 'antd';
import Text from 'antd/lib/typography/Text';
import UploadedModelItem from './uploaded-model-item';
import { Model } from '../../reducers/interfaces';
interface Props {
registeredUsers: any[];
models: Model[];
deleteModel(id: number): void;
}
export default function UploadedModelsListComponent(props: Props) {
const items = props.models.map((model) => {
const owner = props.registeredUsers.filter((user) => user.id === model.ownerID)[0];
return (
<UploadedModelItem
key={model.id as number}
owner={owner}
model={model}
onDelete={() => props.deleteModel(model.id as number)}
/>
);
});
return (
<>
<Row type='flex' justify='center' align='middle'>
<Col md={22} lg={18} xl={16} xxl={14}>
<Text className='cvat-black-color' strong>{'Uploaded by a user'}</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col md={22} lg={18} xl={16} xxl={14} className='cvat-models-list'>
<Row type='flex' align='middle' style={{padding: '10px'}}>
<Col span={4} xxl={3}>
<Text strong>{'Framework'}</Text>
</Col>
<Col span={6} xxl={7}>
<Text strong>{'Name'}</Text>
</Col>
<Col span={3}>
<Text strong>Owner</Text>
</Col>
<Col span={4}>
<Text strong>Uploaded</Text>
</Col>
<Col span={5}>
<Text strong>Labels</Text>
</Col>
<Col span={2}/>
</Row>
{ items }
</Col>
</Row>
</>
);
}

@ -3,6 +3,8 @@ import React from 'react';
import {
Row,
Col,
Tag,
Icon,
Modal,
Button,
} from 'antd';
@ -16,6 +18,7 @@ import UserSelector from './user-selector';
import LabelsEditorComponent from '../labels-editor/labels-editor';
import getCore from '../../core';
import patterns from '../../utils/validation-patterns';
import { getReposData, syncRepos } from '../../utils/git-utils';
const core = getCore();
@ -30,17 +33,24 @@ interface Props {
interface State {
name: string;
bugTracker: string;
repository: string;
repositoryStatus: string;
}
export default class DetailsComponent extends React.PureComponent<Props, State> {
private mounted: boolean;
constructor(props: Props) {
super(props);
const { taskInstance } = props;
this.mounted = false;
this.state = {
name: taskInstance.name,
bugTracker: taskInstance.bugTracker,
repository: '',
repositoryStatus: '',
};
}
@ -84,24 +94,24 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
<>
<Row type='flex' justify='start' align='middle'>
<Col span={12}>
<Text strong className='cvat-black-color'> Overlap size </Text>
<Text strong className='cvat-black-color'>{'Overlap size'}</Text>
<br/>
<Text className='cvat-black-color'>{overlap}</Text>
</Col>
<Col span={12}>
<Text strong className='cvat-black-color'> Segment size </Text>
<Text strong className='cvat-black-color'>{'Segment size'}</Text>
<br/>
<Text className='cvat-black-color'>{segmentSize}</Text>
</Col>
</Row>
<Row type='flex' justify='space-between' align='middle'>
<Col span={12}>
<Text strong className='cvat-black-color'> Image quality </Text>
<Text strong className='cvat-black-color'>{'Image quality'}</Text>
<br/>
<Text className='cvat-black-color'>{imageQuality}</Text>
</Col>
<Col span={12}>
<Text strong className='cvat-black-color'> Z-order </Text>
<Text strong className='cvat-black-color'>{'Z-order'}</Text>
<br/>
<Text className='cvat-black-color'>{zOrder}</Text>
</Col>
@ -150,6 +160,51 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
);
}
private renderDatasetRepository() {
const { repository } = this.state;
const { repositoryStatus } = this.state;
return (
repository ?
<Row>
<Col className='cvat-dataset-repository-url'>
<Text strong className='cvat-black-color'>{'Dataset Repository'}</Text>
<br/>
<a href={repository} target='_blank'>{repository}</a>
{repositoryStatus === 'sync' ?
<Tag color='blue'>
<Icon type='check-circle'/> Synchronized
</Tag> : repositoryStatus === 'merged' ?
<Tag color='green'>
<Icon type='check-circle'/> Merged
</Tag> : repositoryStatus === 'syncing' ?
<Tag color='purple'>
<Icon type='loading'/> Syncing </Tag> :
<Tag color='red' onClick={() => {
this.setState({
repositoryStatus: 'syncing',
});
syncRepos(this.props.taskInstance.id).then(() => {
if (this.mounted) {
this.setState({
repositoryStatus: 'sync',
});
}
}).catch(() => {
if (this.mounted) {
this.setState({
repositoryStatus: '!sync',
});
}
});
}}> <Icon type='warning'/> Synchronize </Tag>
}
</Col>
</Row> : null
);
}
private renderBugTracker() {
const { taskInstance } = this.props;
const { bugTracker } = this.state;
@ -174,7 +229,7 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
return (
<Row>
<Col>
<Text strong className='cvat-black-color'> Issue Tracker </Text>
<Text strong className='cvat-black-color'>{'Issue Tracker'}</Text>
<br/>
<Text editable={{onChange: onChangeValue}}>{bugTracker}</Text>
<Button type='ghost' size='small' onClick={() => {
@ -187,7 +242,7 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
return (
<Row>
<Col>
<Text strong className='cvat-black-color'> Issue Tracker </Text>
<Text strong className='cvat-black-color'>{'Issue Tracker'}</Text>
<br/>
<Text editable={{onChange: onChangeValue}}>{'Not specified'}</Text>
</Col>
@ -219,6 +274,40 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
);
}
public componentDidMount() {
this.mounted = true;
getReposData(this.props.taskInstance.id)
.then((data) => {
if (data !== null && this.mounted) {
if (data.status.error) {
Modal.error({
title: 'Could not receive repository status',
content: data.status.error
});
} else {
this.setState({
repositoryStatus: data.status.value,
});
}
this.setState({
repository: data.url,
});
}
}).catch((error) => {
if (this.mounted) {
Modal.error({
title: 'Could not receive repository status',
content: error.toString(),
});
}
});
}
public componentWillUnmount() {
this.mounted = false;
}
public componentDidUpdate(prevProps: Props) {
if (prevProps !== this.props) {
this.setState({
@ -252,6 +341,7 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
<Col md={16} lg={17} xl={17} xxl={18}>
{ this.renderUsers() }
{ this.renderBugTracker() }
{ this.renderDatasetRepository() }
{ this.renderLabelsEditor() }
</Col>
</Row>

@ -9,14 +9,16 @@ import {
Modal,
} from 'antd';
import TopBarContainer from '../../containers/task-page/top-bar';
import TopBarComponent from './top-bar';
import DetailsContainer from '../../containers/task-page/details';
import JobListContainer from '../../containers/task-page/job-list';
import { Task } from '../../reducers/interfaces';
interface TaskPageComponentProps {
taskInstance: any;
task: Task;
taskFetchingError: string;
taskUpdatingError: string;
taskDeletingError: string;
deleteActivity: boolean | null;
installedGit: boolean;
onFetchTask: (tid: number) => void;
@ -45,12 +47,18 @@ class TaskPageComponent extends React.PureComponent<Props> {
content: this.props.taskUpdatingError,
});
}
if (this.props.taskDeletingError) {
Modal.error({
title: `Could not delete the task ${id}`,
content: this.props.taskDeletingError,
});
}
}
public render() {
const { id } = this.props.match.params;
const fetchTask = !this.props.taskInstance && !this.props.taskFetchingError
|| (this.props.taskInstance && this.props.taskInstance.id !== +id );
const fetchTask = !this.props.task && !this.props.taskFetchingError;
if (fetchTask) {
this.props.onFetchTask(+id);
@ -65,9 +73,9 @@ class TaskPageComponent extends React.PureComponent<Props> {
return (
<Row type='flex' justify='center' align='top' className='cvat-task-details-wrapper'>
<Col md={22} lg={18} xl={16} xxl={14}>
<TopBarContainer/>
<DetailsContainer/>
<JobListContainer/>
<TopBarComponent taskInstance={this.props.task.instance}/>
<DetailsContainer task={this.props.task}/>
<JobListContainer task={this.props.task}/>
</Col>
</Row>
);

@ -10,47 +10,30 @@ import {
import Text from 'antd/lib/typography/Text';
import ActionsMenu from '../actions-menu/actions-menu';
import ActionsMenuContainer from '../../containers/actions-menu/actions-menu';
interface DetailsComponentProps {
taskInstance: any;
loaders: any[];
dumpers: any[];
loadActivity: string | null;
dumpActivities: string[] | null;
installedTFAnnotation: boolean;
installedAutoAnnotation: boolean;
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void;
onDumpAnnotation: (task: any, dumper: any) => void;
onDeleteTask: (task: any) => void;
}
export default function DetailsComponent(props: DetailsComponentProps) {
const subMenuIcon = () => (<img src='/assets/icon-sub-menu.svg'/>);
const { id } = props.taskInstance;
return (
<Row className='cvat-task-top-bar' type='flex' justify='space-between' align='middle'>
<Col>
<Text className='cvat-title'> Task details #{id} </Text>
<Text className='cvat-title'>{`Task details #${id}`}</Text>
</Col>
<Col>
<Dropdown overlay={
ActionsMenu({
taskInstance: props.taskInstance,
loaders: props.loaders,
dumpers: props.dumpers,
loadActivity: props.loadActivity,
dumpActivities: props.dumpActivities,
installedTFAnnotation: props.installedTFAnnotation,
installedAutoAnnotation: props.installedAutoAnnotation,
onLoadAnnotation: props.onLoadAnnotation,
onDumpAnnotation: props.onDumpAnnotation,
onDeleteTask: props.onDeleteTask,
})
}>
<ActionsMenuContainer
taskInstance={props.taskInstance}
/>
}>
<Button size='large' className='cvat-flex cvat-flex-center'>
<Text className='cvat-black-color'> Actions </Text>
<Text className='cvat-black-color'>Actions</Text>
<Icon className='cvat-task-item-menu-icon' component={subMenuIcon}/>
</Button>
</Dropdown>

@ -20,17 +20,17 @@ export default function EmptyListComponent() {
</Row>
<Row type='flex' justify='center' align='middle'>
<Col>
<Text strong> No tasks created yet ... </Text>
<Text strong>{'No tasks created yet ...'}</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col>
<Text type='secondary'> To get started with your annotation project </Text>
<Text type='secondary'>{'To get started with your annotation project'}</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col>
<Link to='/tasks/create'> create new task </Link>
<Link to='/tasks/create'>{'create a new task'}</Link>
</Col>
</Row>
</div>

@ -14,21 +14,12 @@ import {
import moment from 'moment';
import ActionsMenu from '../actions-menu/actions-menu';
import ActionsMenuContainer from '../../containers/actions-menu/actions-menu';
export interface TaskItemProps {
installedTFAnnotation: boolean;
installedAutoAnnotation: boolean;
taskInstance: any;
previewImage: string;
dumpActivities: string[] | null;
loadActivity: string | null;
loaders: any[];
dumpers: any[];
deleted: boolean;
onDeleteTask: (taskInstance: any) => void;
onDumpAnnotation: (task: any, dumper: any) => void;
onLoadAnnotation: (task: any, loader: any, file: File) => void;
}
class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteComponentProps> {
@ -47,7 +38,7 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
}
private renderDescription() {
// Task info
// Task info
const task = this.props.taskInstance;
const { id } = task;
const owner = task.owner ? task.owner.username : null;
@ -67,7 +58,7 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
</Text> <br/>
</> : null
}
<Text type='secondary'> Last updated {updated} </Text>
<Text type='secondary'>{`Last updated ${updated}`}</Text>
</Col>
)
}
@ -92,10 +83,10 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
<circle cx='4' cy='4' r='4' strokeWidth='0'/>
</svg>
{ numOfCompleted === numOfJobs ?
<Text strong className={progressColor}> Completed </Text>
<Text strong className={progressColor}>{'Completed'}</Text>
: numOfCompleted ?
<Text strong className={progressColor}> In Progress </Text>
: <Text strong className={progressColor}> Pending </Text>
<Text strong className={progressColor}>{'In Progress'}</Text>
: <Text strong className={progressColor}>{'Pending'}</Text>
}
</Col>
<Col>
@ -131,20 +122,11 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
</Row>
<Row type='flex' justify='end'>
<Col>
<Text className='cvat-black-color'> Actions </Text>
<Text className='cvat-black-color'>Actions</Text>
<Dropdown overlay={
ActionsMenu({
taskInstance: this.props.taskInstance,
loaders: this.props.loaders,
dumpers: this.props.dumpers,
loadActivity: this.props.loadActivity,
dumpActivities: this.props.dumpActivities,
installedTFAnnotation: this.props.installedTFAnnotation,
installedAutoAnnotation: this.props.installedAutoAnnotation,
onLoadAnnotation: this.props.onLoadAnnotation,
onDumpAnnotation: this.props.onDumpAnnotation,
onDeleteTask: this.props.onDeleteTask,
})
<ActionsMenuContainer
taskInstance={this.props.taskInstance}
/>
}>
<Icon className='cvat-task-item-menu-icon' component={subMenuIcon}/>
</Dropdown>

@ -124,6 +124,12 @@ class TasksPageComponent extends React.PureComponent<TasksPageProps & RouteCompo
gettingQuery[field] = value;
}
}
} else {
if (field === 'page') {
gettingQuery[field] = 1;
} else {
gettingQuery[field] = null;
}
}
}

@ -16,37 +16,35 @@ interface VisibleTopBarProps {
searchValue: string;
}
class TopBarComponent extends React.PureComponent<VisibleTopBarProps & RouteComponentProps> {
public render() {
return (
<>
<Row type='flex' justify='center' align='middle'>
<Col md={22} lg={18} xl={16} xxl={14}>
<Text strong> Default project </Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col md={11} lg={9} xl={8} xxl={7}>
<Text className='cvat-title'> Tasks </Text>
<Input.Search
defaultValue={this.props.searchValue}
onSearch={this.props.onSearch}
size='large' placeholder='Search'
/>
</Col>
<Col
md={{span: 11}}
lg={{span: 9}}
xl={{span: 8}}
xxl={{span: 7}}>
<Button size='large' id='cvat-create-task-button' type='primary' onClick={
() => this.props.history.push('/tasks/create')
}> Create new task </Button>
</Col>
</Row>
</>
)
}
function TopBarComponent(props: VisibleTopBarProps & RouteComponentProps) {
return (
<>
<Row type='flex' justify='center' align='middle'>
<Col md={22} lg={18} xl={16} xxl={14}>
<Text strong>{'Default project'}</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Col md={11} lg={9} xl={8} xxl={7}>
<Text className='cvat-title'>Tasks</Text>
<Input.Search
defaultValue={props.searchValue}
onSearch={props.onSearch}
size='large' placeholder='Search'
/>
</Col>
<Col
md={{span: 11}}
lg={{span: 9}}
xl={{span: 8}}
xxl={{span: 7}}>
<Button size='large' id='cvat-create-task-button' type='primary' onClick={
() => props.history.push('/tasks/create')
}> Create new task </Button>
</Col>
</Row>
</>
)
}
export default withRouter(TopBarComponent);
export default withRouter(TopBarComponent);

@ -0,0 +1,91 @@
import React from 'react';
import { connect } from 'react-redux';
import ActionsMenuComponent from '../../components/actions-menu/actions-menu';
import { CombinedState } from '../../reducers/interfaces';
import { showRunModelDialog } from '../../actions/models-actions';
import {
dumpAnnotationsAsync,
loadAnnotationsAsync,
deleteTaskAsync,
} from '../../actions/tasks-actions';
interface OwnProps {
taskInstance: any;
}
interface StateToProps {
loaders: any[];
dumpers: any[];
loadActivity: string | null;
dumpActivities: string[] | null;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
installedAutoAnnotation: boolean;
};
interface DispatchToProps {
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void;
onDumpAnnotation: (taskInstance: any, dumper: any) => void;
onDeleteTask: (taskInstance: any) => void;
onOpenRunWindow: (taskInstance: any) => void;
}
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const { formats } = state;
const { dumps } = state.tasks.activities;
const { loads } = state.tasks.activities;
const { plugins } = state.plugins;
const id = own.taskInstance.id;
return {
installedTFAnnotation: plugins.TF_ANNOTATION,
installedTFSegmentation: plugins.TF_SEGMENTATION,
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
dumpActivities: dumps.byTask[id] ? dumps.byTask[id] : null,
loadActivity: loads.byTask[id] ? loads.byTask[id] : null,
loaders: formats.loaders,
dumpers: formats.dumpers,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => {
dispatch(loadAnnotationsAsync(taskInstance, loader, file));
},
onDumpAnnotation: (taskInstance: any, dumper: any) => {
dispatch(dumpAnnotationsAsync(taskInstance, dumper));
},
onDeleteTask: (taskInstance: any) => {
dispatch(deleteTaskAsync(taskInstance));
},
onOpenRunWindow: (taskInstance: any) => {
dispatch(showRunModelDialog(taskInstance));
}
};
}
function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps) {
return (
<ActionsMenuComponent
taskInstance={props.taskInstance}
loaders={props.loaders}
dumpers={props.dumpers}
loadActivity={props.loadActivity}
dumpActivities={props.dumpActivities}
installedTFAnnotation={props.installedTFAnnotation}
installedTFSegmentation={props.installedTFSegmentation}
installedAutoAnnotation={props.installedAutoAnnotation}
onLoadAnnotation={props.onLoadAnnotation}
onDumpAnnotation={props.onDumpAnnotation}
onDeleteTask={props.onDeleteTask}
onOpenRunWindow={props.onOpenRunWindow}
/>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ActionsMenuContainer);

@ -0,0 +1,53 @@
import React from 'react';
import { connect } from 'react-redux';
import CreateModelPageComponent from '../../components/create-model-page/create-model-page';
import { createModelAsync } from '../../actions/models-actions';
import {
ModelFiles,
CombinedState,
} from '../../reducers/interfaces';
interface StateToProps {
isAdmin: boolean;
modelCreatingError: any;
modelCreatingStatus: string;
}
interface DispatchToProps {
createModel(name: string, files: ModelFiles, global: boolean): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const { models} = state;
return {
isAdmin: state.auth.user.isAdmin,
modelCreatingError: models.creatingError,
modelCreatingStatus: models.creatingStatus,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
createModel(name: string, files: ModelFiles, global: boolean) {
dispatch(createModelAsync(name, files, global));
},
};
}
function CreateModelPageContainer(props: StateToProps & DispatchToProps) {
return (
<CreateModelPageComponent
isAdmin={props.isAdmin}
modelCreatingError={props.modelCreatingError ? props.modelCreatingError.toString() : ''}
modelCreatingStatus={props.modelCreatingStatus}
createModel={props.createModel}
/>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(CreateModelPageContainer);

@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { CombinedState } from '../../reducers/root-reducer';
import { CombinedState } from '../../reducers/interfaces';
import CreateTaskComponent from '../../components/create-task-page/create-task-page';
import { CreateTaskData } from '../../components/create-task-page/create-task-content';
import { createTaskAsync } from '../../actions/tasks-actions';

@ -5,8 +5,14 @@ import { TreeNodeNormal } from 'antd/lib/tree/Tree'
import FileManagerComponent, { Files } from '../../components/file-manager/file-manager';
import { loadShareDataAsync } from '../../actions/share-actions';
import { ShareItem } from '../../reducers/interfaces';
import { CombinedState } from '../../reducers/root-reducer';
import {
ShareItem,
CombinedState,
} from '../../reducers/interfaces';
interface OwnProps {
withRemote: boolean;
}
interface StateToProps {
treeData: TreeNodeNormal[];
@ -43,7 +49,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
};
}
class FileManagerContainer extends React.PureComponent<StateToProps & DispatchToProps> {
type Props = StateToProps & DispatchToProps & OwnProps;
export class FileManagerContainer extends React.PureComponent<Props> {
private managerComponentRef: any;
public getFiles(): Files {
@ -59,6 +67,7 @@ class FileManagerContainer extends React.PureComponent<StateToProps & DispatchTo
<FileManagerComponent
treeData={this.props.treeData}
onLoadData={this.props.getTreeData}
withRemote={this.props.withRemote}
ref={(component) => this.managerComponentRef = component}
/>
);

@ -2,14 +2,18 @@ import React from 'react';
import { connect } from 'react-redux';
import { logoutAsync } from '../../actions/auth-actions';
import { CombinedState } from '../../reducers/root-reducer';
import { SupportedPlugins } from '../../reducers/interfaces';
import {
SupportedPlugins,
CombinedState,
} from '../../reducers/interfaces';
import HeaderComponent from '../../components/header/header';
interface StateToProps {
installedAnalytics: boolean;
installedAutoAnnotation: boolean;
installedTFSegmentation: boolean;
installedTFAnnotation: boolean;
username: string;
logoutError: any;
}
@ -24,6 +28,8 @@ function mapStateToProps(state: CombinedState): StateToProps {
return {
installedAnalytics: plugins[SupportedPlugins.ANALYTICS],
installedAutoAnnotation: plugins[SupportedPlugins.AUTO_ANNOTATION],
installedTFSegmentation: plugins[SupportedPlugins.TF_SEGMENTATION],
installedTFAnnotation: plugins[SupportedPlugins.TF_ANNOTATION],
username: auth.user.username,
logoutError: auth.logoutError,
};
@ -39,6 +45,8 @@ function HeaderContainer(props: StateToProps & DispatchToProps) {
return (
<HeaderComponent
installedAnalytics={props.installedAnalytics}
installedTFAnnotation={props.installedTFAnnotation}
installedTFSegmentation={props.installedTFSegmentation}
installedAutoAnnotation={props.installedAutoAnnotation}
onLogout={props.logout}
username={props.username}

@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { loginAsync } from '../../actions/auth-actions';
import { CombinedState } from '../../reducers/root-reducer';
import { CombinedState } from '../../reducers/interfaces';
import LoginPageComponent from '../../components/login-page/login-page';
interface StateToProps {

@ -0,0 +1,93 @@
import React from 'react';
import { connect } from 'react-redux';
import ModelRunnerModalComponent from '../../components/model-runner-modal/model-runner-modal';
import {
Model,
CombinedState,
} from '../../reducers/interfaces';
import {
getModelsAsync,
inferModelAsync,
closeRunModelDialog,
} from '../../actions/models-actions';
interface StateToProps {
startingError: any;
modelsInitialized: boolean;
models: Model[];
activeProcesses: {
[index: string]: string
};
taskInstance: any;
visible: boolean;
}
interface DispatchToProps {
inferModelAsync(
taskInstance: any,
model: Model,
mapping: {
[index: string]: string
},
cleanOut: boolean,
): void;
getModels(): void;
closeDialog(): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const { models } = state;
return {
modelsInitialized: models.initialized,
models: models.models,
activeProcesses: {},
taskInstance: models.activeRunTask,
visible: models.visibleRunWindows,
startingError: models.startingError,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return ({
inferModelAsync(
taskInstance: any,
model: Model,
mapping: {
[index: string]: string
},
cleanOut: boolean): void {
dispatch(inferModelAsync(taskInstance, model, mapping, cleanOut));
},
getModels(): void {
dispatch(getModelsAsync());
},
closeDialog(): void {
dispatch(closeRunModelDialog());
}
});
}
function ModelRunnerModalContainer(props: StateToProps & DispatchToProps) {
return (
<ModelRunnerModalComponent
modelsInitialized={props.modelsInitialized}
models={props.models}
activeProcesses={props.activeProcesses}
visible={props.visible}
taskInstance={props.taskInstance}
getModels={props.getModels}
closeDialog={props.closeDialog}
runInference={props.inferModelAsync}
startingError={props.startingError ? props.startingError.toString() : ''}
/>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
) (ModelRunnerModalContainer);

@ -1,13 +1,79 @@
import React from 'react';
import { connect } from 'react-redux';
export default class ModelsPageContainer extends React.PureComponent {
constructor(props: any) {
super(props);
}
public render() {
return (
<div> Models page </div>
)
}
}
import ModelsPageComponent from '../../components/models-page/models-page';
import {
Model,
CombinedState,
} from '../../reducers/interfaces';
import {
getModelsAsync,
deleteModelAsync,
} from '../../actions/models-actions';
interface StateToProps {
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
modelsAreBeingFetched: boolean;
modelsFetchingError: any;
models: Model[];
registeredUsers: any[];
}
interface DispatchToProps {
getModels(): void;
deleteModel(id: number): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const { plugins } = state.plugins;
const { models } = state;
return {
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
installedTFAnnotation: plugins.TF_ANNOTATION,
installedTFSegmentation: plugins.TF_SEGMENTATION,
modelsAreBeingFetched: !models.initialized,
modelsFetchingError: models.fetchingError,
models: models.models,
registeredUsers: state.users.users,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
getModels() {
dispatch(getModelsAsync());
},
deleteModel(id: number) {
dispatch(deleteModelAsync(id));
},
};
}
function ModelsPageContainer(props: DispatchToProps & StateToProps) {
const render = props.installedAutoAnnotation
|| props.installedTFAnnotation
|| props.installedTFSegmentation;
return (
render ?
<ModelsPageComponent
installedAutoAnnotation={props.installedAutoAnnotation}
installedTFSegmentation={props.installedTFSegmentation}
installedTFAnnotation={props.installedTFAnnotation}
modelsAreBeingFetched={props.modelsAreBeingFetched}
modelsFetchingError={props.modelsFetchingError}
registeredUsers={props.registeredUsers}
models={props.models}
getModels={props.getModels}
deleteModel={props.deleteModel}
/> : null
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ModelsPageContainer);

@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { registerAsync } from '../../actions/auth-actions';
import { CombinedState } from '../../reducers/root-reducer';
import { CombinedState } from '../../reducers/interfaces';
import RegisterPageComponent from '../../components/register-page/register-page';
interface StateToProps {

@ -2,12 +2,17 @@ import React from 'react';
import { connect } from 'react-redux';
import DetailsComponent from '../../components/task-page/details';
import { CombinedState } from '../../reducers/root-reducer';
import { updateTaskAsync } from '../../actions/task-actions';
import { updateTaskAsync } from '../../actions/tasks-actions';
import {
Task,
CombinedState,
} from '../../reducers/interfaces';
interface OwnProps {
task: Task;
}
interface StateToProps {
previewImage: string;
taskInstance: any;
registeredUsers: any[];
installedGit: boolean;
}
@ -16,15 +21,11 @@ interface DispatchToProps {
onTaskUpdate: (taskInstance: any) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const { plugins } = state.plugins;
const taskInstance = (state.activeTask.task as any).instance;
const previewImage = (state.activeTask.task as any).preview;
return {
registeredUsers: state.users.users,
taskInstance,
previewImage,
installedGit: plugins.GIT_INTEGRATION,
};
}
@ -38,11 +39,11 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
}
function TaskPageContainer(props: StateToProps & DispatchToProps) {
function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps) {
return (
<DetailsComponent
previewImage={props.previewImage}
taskInstance={props.taskInstance}
previewImage={props.task.preview}
taskInstance={props.task.instance}
installedGit={props.installedGit}
onTaskUpdate={props.onTaskUpdate}
registeredUsers={props.registeredUsers}

@ -2,11 +2,17 @@ import React from 'react';
import { connect } from 'react-redux';
import JobListComponent from '../../components/task-page/job-list';
import { CombinedState } from '../../reducers/root-reducer';
import { updateJobAsync } from '../../actions/task-actions';
import { updateJobAsync } from '../../actions/tasks-actions';
import {
Task,
CombinedState,
} from '../../reducers/interfaces';
interface OwnProps {
task: Task;
}
interface StateToProps {
taskInstance: any;
registeredUsers: any[];
}
@ -16,7 +22,6 @@ interface DispatchToProps {
function mapStateToProps(state: CombinedState): StateToProps {
return {
taskInstance: state.activeTask.task ? state.activeTask.task.instance : null,
registeredUsers: state.users.users,
};
}
@ -27,10 +32,10 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
};
}
function TaskPageContainer(props: StateToProps & DispatchToProps) {
function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps) {
return (
<JobListComponent
taskInstance={props.taskInstance}
taskInstance={props.task.instance}
registeredUsers={props.registeredUsers}
onJobUpdate={props.onJobUpdate}
/>

@ -1,15 +1,23 @@
import React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { RouteComponentProps } from 'react-router';
import { getTaskAsync } from '../../actions/task-actions';
import { getTasksAsync } from '../../actions/tasks-actions';
import TaskPageComponent from '../../components/task-page/task-page';
import { CombinedState } from '../../reducers/root-reducer';
import {
Task,
CombinedState,
} from '../../reducers/interfaces';
type Props = RouteComponentProps<{id: string}>;
interface StateToProps {
task: Task;
taskFetchingError: any;
taskUpdatingError: any;
taskInstance: any;
taskDeletingError: any;
deleteActivity: boolean | null;
installedGit: boolean;
}
@ -18,23 +26,25 @@ interface DispatchToProps {
fetchTask: (tid: number) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
function mapStateToProps(state: CombinedState, own: Props): StateToProps {
const { plugins } = state.plugins;
const { activeTask } = state;
const { deletes } = state.tasks.activities;
const taskDeletingError = deletes.deletingError;
const id = +own.match.params.id;
const taskInstance = activeTask.task ? activeTask.task.instance : null;
const filtered = state.tasks.current.filter((task) => task.instance.id === id);
const task = filtered[0] || null;
let deleteActivity = null;
if (taskInstance) {
const { id } = taskInstance;
deleteActivity = deletes.byTask[id] ? deletes.byTask[id] : null;
if (task && id in deletes.byTask) {
deleteActivity = deletes.byTask[id];
}
return {
taskInstance,
taskFetchingError: activeTask.taskFetchingError,
taskUpdatingError: activeTask.taskUpdatingError,
task,
taskFetchingError: state.tasks.tasksFetchingError,
taskUpdatingError: state.tasks.taskUpdatingError,
taskDeletingError,
deleteActivity,
installedGit: plugins.GIT_INTEGRATION,
};
@ -43,7 +53,16 @@ function mapStateToProps(state: CombinedState): StateToProps {
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
fetchTask: (tid: number) => {
dispatch(getTaskAsync(tid));
dispatch(getTasksAsync({
id: tid,
page: 1,
search: null,
owner: null,
assignee: null,
name: null,
status: null,
mode: null,
}));
},
};
}
@ -51,7 +70,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
function TaskPageContainer(props: StateToProps & DispatchToProps) {
return (
<TaskPageComponent
taskInstance={props.taskInstance}
task={props.task}
taskDeletingError={props.taskDeletingError ? props.taskDeletingError.toString() : ''}
taskFetchingError={props.taskFetchingError ? props.taskFetchingError.toString() : ''}
taskUpdatingError={props.taskUpdatingError ? props.taskUpdatingError.toString() : ''}
deleteActivity={props.deleteActivity}
@ -61,7 +81,7 @@ function TaskPageContainer(props: StateToProps & DispatchToProps) {
);
}
export default connect(
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps,
)(TaskPageContainer);
)(TaskPageContainer));

@ -1,86 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import {
dumpAnnotationsAsync,
loadAnnotationsAsync,
deleteTaskAsync,
} from '../../actions/tasks-actions';
import TopBarComponent from '../../components/task-page/top-bar';
import { CombinedState } from '../../reducers/root-reducer';
interface StateToProps {
taskInstance: any;
loaders: any[];
dumpers: any[];
loadActivity: string | null;
dumpActivities: string[] | null;
installedTFAnnotation: boolean;
installedAutoAnnotation: boolean;
}
interface DispatchToProps {
deleteTask: (taskInstance: any) => void;
dumpAnnotations: (task: any, format: string) => void;
loadAnnotations: (task: any, format: string, file: File) => void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const taskInstance = (state.activeTask.task as any).instance;
const { plugins } = state.plugins;
const { formats } = state;
const { dumps } = state.tasks.activities;
const { loads } = state.tasks.activities;
const { id } = taskInstance;
const dumpActivities = dumps.byTask[id] ? dumps.byTask[id] : null;
const loadActivity = loads.byTask[id] ? loads.byTask[id] : null;
return {
taskInstance,
loaders: formats.loaders,
dumpers: formats.dumpers,
dumpActivities,
loadActivity,
installedTFAnnotation: plugins.TF_ANNOTATION,
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
deleteTask: (taskInstance: any): void => {
dispatch(deleteTaskAsync(taskInstance));
},
dumpAnnotations: (task: any, dumper: any): void => {
dispatch(dumpAnnotationsAsync(task, dumper));
},
loadAnnotations: (task: any, loader: any, file: File): void => {
dispatch(loadAnnotationsAsync(task, loader, file));
},
};
}
function TaskPageContainer(props: StateToProps & DispatchToProps) {
return (
<TopBarComponent
taskInstance={props.taskInstance}
loaders={props.loaders}
dumpers={props.dumpers}
loadActivity={props.loadActivity}
dumpActivities={props.dumpActivities}
installedTFAnnotation={props.installedTFAnnotation}
installedAutoAnnotation={props.installedAutoAnnotation}
onDeleteTask={props.deleteTask}
onDumpAnnotation={props.dumpAnnotations}
onLoadAnnotation={props.loadAnnotations}
/>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(TaskPageContainer);

@ -3,39 +3,23 @@ import { connect } from 'react-redux';
import {
TasksQuery,
SupportedPlugins,
} from '../../reducers/interfaces';
import {
CombinedState,
} from '../../reducers/root-reducer';
} from '../../reducers/interfaces';
import TaskItemComponent from '../../components/tasks-page/task-item'
import {
getTasksAsync,
dumpAnnotationsAsync,
loadAnnotationsAsync,
deleteTaskAsync,
} from '../../actions/tasks-actions';
interface StateToProps {
installedTFAnnotation: boolean;
installedAutoAnnotation: boolean;
dumpActivities: string[] | null;
loadActivity: string | null;
deleteActivity: boolean | null;
previewImage: string;
taskInstance: any;
loaders: any[];
dumpers: any[];
}
interface DispatchToProps {
getTasks: (query: TasksQuery) => void;
delete: (taskInstance: any) => void;
dump: (task: any, format: string) => void;
load: (task: any, format: string, file: File) => void;
}
interface OwnProps {
@ -45,23 +29,13 @@ interface OwnProps {
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const task = state.tasks.current[own.idx];
const { formats } = state;
const { dumps } = state.tasks.activities;
const { loads } = state.tasks.activities;
const { deletes } = state.tasks.activities;
const { plugins } = state.plugins;
const id = own.taskID;
return {
installedTFAnnotation: plugins.TF_ANNOTATION,
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
dumpActivities: dumps.byTask[id] ? dumps.byTask[id] : null,
loadActivity: loads.byTask[id] ? loads.byTask[id] : null,
deleteActivity: deletes.byTask[id] ? deletes.byTask[id] : null,
previewImage: task.preview,
taskInstance: task.instance,
loaders: formats.loaders,
dumpers: formats.dumpers,
};
}
@ -70,15 +44,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
getTasks: (query: TasksQuery): void => {
dispatch(getTasksAsync(query));
},
dump: (task: any, dumper: any): void => {
dispatch(dumpAnnotationsAsync(task, dumper));
},
load: (task: any, loader: any, file: File): void => {
dispatch(loadAnnotationsAsync(task, loader, file));
},
delete: (taskInstance: any): void => {
dispatch(deleteTaskAsync(taskInstance));
},
}
}
@ -87,18 +52,9 @@ type TasksItemContainerProps = StateToProps & DispatchToProps & OwnProps;
function TaskItemContainer(props: TasksItemContainerProps) {
return (
<TaskItemComponent
installedTFAnnotation={props.installedTFAnnotation}
installedAutoAnnotation={props.installedAutoAnnotation}
deleted={props.deleteActivity === true}
taskInstance={props.taskInstance}
previewImage={props.previewImage}
dumpActivities={props.dumpActivities}
loadActivity={props.loadActivity}
loaders={props.loaders}
dumpers={props.dumpers}
onDeleteTask={props.delete}
onLoadAnnotation={props.load}
onDumpAnnotation={props.dump}
/>
);
}

@ -4,11 +4,8 @@ import { connect } from 'react-redux';
import {
TasksState,
TasksQuery,
} from '../../reducers/interfaces';
import {
CombinedState,
} from '../../reducers/root-reducer';
} from '../../reducers/interfaces';
import TasksListComponent from '../../components/tasks-page/task-list';

@ -3,8 +3,8 @@ import { connect } from 'react-redux';
import {
TasksQuery,
CombinedState
} from '../../reducers/interfaces';
import { CombinedState } from '../../reducers/root-reducer';
import TasksPageComponent from '../../components/tasks-page/tasks-page';

@ -3,16 +3,19 @@ import ReactDOM from 'react-dom';
import { connect, Provider } from 'react-redux';
import CVATApplication from './components/cvat-app';
import createCVATStore from './store';
import createRootReducer from './reducers/root-reducer';
import createCVATStore, { getCVATStore } from './store';
import { authorizedAsync } from './actions/auth-actions';
import { gettingFormatsAsync } from './actions/formats-actions';
import { checkPluginsAsync } from './actions/plugins-actions';
import { getUsersAsync } from './actions/users-actions';
import { CombinedState } from './reducers/root-reducer';
import { CombinedState } from './reducers/interfaces';
const cvatStore = createCVATStore();
createCVATStore(createRootReducer);
const cvatStore = getCVATStore();
interface StateToProps {
pluginsInitialized: boolean;
@ -22,6 +25,9 @@ interface StateToProps {
gettingAuthError: any;
gettingFormatsError: any;
gettingUsersError: any;
installedAutoAnnotation: boolean;
installedTFSegmentation: boolean;
installedTFAnnotation: boolean;
user: any;
}
@ -46,6 +52,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
gettingAuthError: auth.authError,
gettingUsersError: users.gettingUsersError,
gettingFormatsError: formats.gettingFormatsError,
installedAutoAnnotation: plugins.plugins.AUTO_ANNOTATION,
installedTFSegmentation: plugins.plugins.TF_SEGMENTATION,
installedTFAnnotation: plugins.plugins.TF_ANNOTATION,
user: auth.user,
};
}
@ -73,6 +82,9 @@ function reduxAppWrapper(props: StateToProps & DispatchToProps) {
gettingAuthError={props.gettingAuthError ? props.gettingAuthError.toString() : ''}
gettingFormatsError={props.gettingFormatsError ? props.gettingFormatsError.toString() : ''}
gettingUsersError={props.gettingUsersError ? props.gettingUsersError.toString() : ''}
installedAutoAnnotation={props.installedAutoAnnotation}
installedTFSegmentation={props.installedTFSegmentation}
installedTFAnnotation={props.installedTFAnnotation}
user={props.user}
/>
)

@ -27,6 +27,7 @@ export interface Task {
export interface TasksState {
initialized: boolean;
tasksFetchingError: any;
taskUpdatingError: any;
gettingQuery: TasksQuery;
count: number;
current: Task[];
@ -71,6 +72,7 @@ export enum SupportedPlugins {
GIT_INTEGRATION = 'GIT_INTEGRATION',
AUTO_ANNOTATION = 'AUTO_ANNOTATION',
TF_ANNOTATION = 'TF_ANNOTATION',
TF_SEGMENTATION = 'TF_SEGMENTATION',
ANALYTICS = 'ANALYTICS',
}
@ -81,12 +83,6 @@ export interface PluginsState {
};
}
export interface TaskState {
task: Task | null;
taskFetchingError: any;
taskUpdatingError: any;
}
export interface UsersState {
users: any[];
initialized: boolean;
@ -108,3 +104,54 @@ export interface ShareState {
root: ShareItem;
error: any;
}
export interface Model {
id: number | null; // null for preinstalled models
ownerID: number | null; // null for preinstalled models
name: string;
primary: boolean;
uploadDate: string;
updateDate: string;
labels: string[];
}
export interface Running {
[tid: string]: {
status: string;
processId: string;
error: any;
};
}
export interface ModelsState {
initialized: boolean;
creatingStatus: string;
creatingError: any;
startingError: any;
fetchingError: any;
deletingErrors: { // by id
[index: number]: any;
};
models: Model[];
runnings: Running[];
visibleRunWindows: boolean;
activeRunTask: any;
}
export interface ModelFiles {
[key: string]: string | File;
xml: string | File;
bin: string | File;
py: string | File;
json: string | File;
}
export interface CombinedState {
auth: AuthState;
tasks: TasksState;
users: UsersState;
share: ShareState;
formats: FormatsState;
plugins: PluginsState;
models: ModelsState;
}

@ -0,0 +1,125 @@
import { AnyAction } from 'redux';
import { ModelsActionTypes } from '../actions/models-actions';
import { ModelsState } from './interfaces';
const defaultState: ModelsState = {
initialized: false,
creatingStatus: '',
creatingError: null,
startingError: null,
fetchingError: null,
deletingErrors: {},
models: [],
visibleRunWindows: false,
activeRunTask: null,
runnings: [],
};
export default function (state = defaultState, action: AnyAction): ModelsState {
switch (action.type) {
case ModelsActionTypes.GET_MODELS: {
return {
...state,
fetchingError: null,
initialized: false,
};
}
case ModelsActionTypes.GET_MODELS_SUCCESS: {
return {
...state,
models: action.payload.models,
initialized: true,
};
}
case ModelsActionTypes.GET_MODELS_FAILED: {
return {
...state,
fetchingError: action.payload.error,
initialized: true,
};
}
case ModelsActionTypes.DELETE_MODEL: {
const errors = { ...state.deletingErrors };
delete errors[action.payload.id];
return {
...state,
deletingErrors: errors,
};
}
case ModelsActionTypes.DELETE_MODEL_SUCCESS: {
return {
...state,
models: state.models.filter(
(model): boolean => model.id !== action.payload.id,
),
};
}
case ModelsActionTypes.DELETE_MODEL_FAILED: {
const errors = { ...state.deletingErrors };
errors[action.payload.id] = action.payload.error;
return {
...state,
deletingErrors: errors,
};
}
case ModelsActionTypes.CREATE_MODEL: {
return {
...state,
creatingError: null,
creatingStatus: '',
};
}
case ModelsActionTypes.CREATE_MODEL_STATUS_UPDATED: {
return {
...state,
creatingStatus: action.payload.status,
};
}
case ModelsActionTypes.CREATE_MODEL_FAILED: {
return {
...state,
creatingError: action.payload.error,
creatingStatus: '',
};
}
case ModelsActionTypes.CREATE_MODEL_SUCCESS: {
return {
...state,
initialized: false,
creatingStatus: 'CREATED',
};
}
case ModelsActionTypes.INFER_MODEL: {
return {
...state,
startingError: null,
};
}
case ModelsActionTypes.INFER_MODEL_FAILED: {
return {
...state,
startingError: action.payload.error,
};
}
case ModelsActionTypes.SHOW_RUN_MODEL_DIALOG: {
return {
...state,
visibleRunWindows: true,
activeRunTask: action.payload.taskInstance,
};
}
case ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG: {
return {
...state,
visibleRunWindows: false,
activeRunTask: null,
};
}
default: {
return {
...state,
};
}
}
}

@ -1,25 +1,31 @@
import { AnyAction } from 'redux';
import { PluginsActionTypes } from '../actions/plugins-actions';
import { registerGitPlugin } from '../utils/git-utils';
import {
PluginsState,
} from './interfaces';
const defaultState: PluginsState = {
initialized: false,
plugins: {
GIT_INTEGRATION: false,
AUTO_ANNOTATION: false,
TF_ANNOTATION: false,
TF_SEGMENTATION: false,
ANALYTICS: false,
},
};
export default function (state = defaultState, action: AnyAction): PluginsState {
switch (action.type) {
case PluginsActionTypes.CHECKED_ALL_PLUGINS: {
const { plugins } = action.payload;
if (!state.plugins.GIT_INTEGRATION && plugins.GIT_INTEGRATION) {
registerGitPlugin();
}
return {
...state,
initialized: true,

@ -5,27 +5,7 @@ import usersReducer from './users-reducer';
import shareReducer from './share-reducer';
import formatsReducer from './formats-reducer';
import pluginsReducer from './plugins-reducer';
import taskReducer from './task-reducer';
import {
AuthState,
TasksState,
UsersState,
ShareState,
FormatsState,
PluginsState,
TaskState,
} from './interfaces';
export interface CombinedState {
auth: AuthState;
tasks: TasksState;
users: UsersState;
share: ShareState;
formats: FormatsState;
plugins: PluginsState;
activeTask: TaskState;
}
import modelsReducer from './models-reducer';
export default function createRootReducer(): Reducer {
return combineReducers({
@ -35,6 +15,6 @@ export default function createRootReducer(): Reducer {
share: shareReducer,
formats: formatsReducer,
plugins: pluginsReducer,
activeTask: taskReducer,
models: modelsReducer,
});
}

@ -1,65 +0,0 @@
import { AnyAction } from 'redux';
import { TaskActionTypes } from '../actions/task-actions';
import { Task, TaskState } from './interfaces';
const defaultState: TaskState = {
taskFetchingError: null,
taskUpdatingError: null,
task: null,
};
export default function (state = defaultState, action: AnyAction): TaskState {
switch (action.type) {
case TaskActionTypes.GET_TASK:
return {
...state,
taskFetchingError: null,
taskUpdatingError: null,
};
case TaskActionTypes.GET_TASK_SUCCESS: {
return {
...state,
task: {
instance: action.payload.taskInstance,
preview: action.payload.previewImage,
},
};
}
case TaskActionTypes.GET_TASK_FAILED: {
return {
...state,
task: null,
taskFetchingError: action.payload.error,
};
}
case TaskActionTypes.UPDATE_TASK: {
return {
...state,
taskUpdatingError: null,
taskFetchingError: null,
};
}
case TaskActionTypes.UPDATE_TASK_SUCCESS: {
return {
...state,
task: {
...(state.task as Task),
instance: action.payload.taskInstance,
},
};
}
case TaskActionTypes.UPDATE_TASK_FAILED: {
return {
...state,
task: {
...(state.task as Task),
instance: action.payload.taskInstance,
},
taskUpdatingError: action.payload.error,
};
}
default:
return { ...state };
}
}

@ -6,6 +6,7 @@ import { TasksState, Task } from './interfaces';
const defaultState: TasksState = {
initialized: false,
tasksFetchingError: null,
taskUpdatingError: null,
count: 0,
current: [],
gettingQuery: {
@ -44,6 +45,7 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
return {
...stateToResetErrors,
tasksFetchingError: null,
taskUpdatingError: null,
activities: {
...stateToResetErrors.activities,
dumps: {
@ -55,6 +57,10 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
loadingError: null,
loadingDoneMessage: '',
},
deletes: {
...stateToResetErrors.activities.deletes,
deletingError: null,
},
},
};
}
@ -339,6 +345,43 @@ export default (inputState: TasksState = defaultState, action: AnyAction): Tasks
},
};
}
case TasksActionTypes.UPDATE_TASK: {
return {
...state,
taskUpdatingError: null,
};
}
case TasksActionTypes.UPDATE_TASK_SUCCESS: {
return {
...state,
current: state.current.map((task): Task => {
if (task.instance.id === action.payload.taskInstance.id) {
return {
...task,
instance: action.payload.taskInstance,
};
}
return task;
}),
};
}
case TasksActionTypes.UPDATE_TASK_FAILED: {
return {
...state,
taskUpdatingError: action.payload.error,
current: state.current.map((task): Task => {
if (task.instance.id === action.payload.taskInstance.id) {
return {
...task,
instance: action.payload.taskInstance,
};
}
return task;
}),
};
}
default:
return state;
}

@ -1,15 +1,28 @@
import thunk from 'redux-thunk';
import { createStore, applyMiddleware, Store } from 'redux';
import createRootReducer from './reducers/root-reducer';
import {
createStore,
applyMiddleware,
Store,
Reducer,
} from 'redux';
const middlewares = [
thunk,
];
export default function createCVATStore(): Store {
return createStore(
let store: Store | null = null;
export default function createCVATStore(createRootReducer: () => Reducer): void {
store = createStore(
createRootReducer(),
applyMiddleware(...middlewares),
);
}
export function getCVATStore(): Store {
if (store) {
return store;
}
throw new Error('First create a store');
}

@ -1,11 +1,6 @@
#root {
width: 100%;
height: 100%;
min-height: 100%;
display: grid;
}
.cvat-header.ant-layout-header {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding-left: 0px;
padding-right: 0px;
@ -16,24 +11,40 @@
.cvat-left-header {
width: 50%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
justify-content: flex-start;
align-items: center;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.cvat-right-header {
width: 50%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
justify-content: flex-end;
align-items: center;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.cvat-flex {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.cvat-flex-center {
align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.cvat-black-color {
@ -52,18 +63,26 @@
}
.anticon.cvat-logo-icon {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-right: 20px;
}
.anticon.cvat-logo-icon > img {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
height: 15.8px;
margin-left: 18px;
}
.anticon.cvat-back-icon > img {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
height: 15.8px;
margin-left: 18px;
@ -71,19 +90,24 @@
}
.anticon.cvat-back-icon:hover > img {
filter: invert(0.4);
-webkit-filter: invert(0.4);
filter: invert(0.4);
}
.ant-btn.cvat-header-button {
color: black !important;
color: black;
padding: 0px 10px;
margin-right: 10px;
}
.ant-menu.cvat-header-menu {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
border: 0px;
border-left: 1px solid #c3c3c3;
height: 100%;
@ -91,7 +115,7 @@
}
.ant-menu.cvat-header-menu > li.ant-menu-submenu {
border-bottom: unset !important;
border-bottom: unset;
}
@ -115,7 +139,9 @@
.anticon.cvat-header-menu-icon > img {
width: 14px;
transform: rotate(-90deg);
-webkit-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.anticon.cvat-empty-tasks-icon > img {
@ -142,12 +168,18 @@
}
.cvat-tasks-page > div:nth-child(2) > div:nth-child(1) {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.cvat-tasks-page > div:nth-child(2) > div:nth-child(2) {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
justify-content: flex-end;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
}
.cvat-tasks-page > div:nth-child(2) > div:nth-child(1) > span:nth-child(2) {
@ -156,7 +188,10 @@
}
.cvat-tasks-page > span.ant-typography {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.cvat-tasks-page {
@ -213,8 +248,12 @@
}
.ant-pagination.cvat-tasks-pagination {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
justify-content: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.cvat-task-list {
@ -246,6 +285,8 @@
}
.cvat-tasks-list-item > div:nth-child(4) > div:nth-child(2) > div {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
@ -254,23 +295,33 @@
}
.cvat-task-item-menu-icon > img:hover {
filter: invert(0.5);
-webkit-filter: invert(0.5);
filter: invert(0.5);
}
.cvat-task-item-menu > hr {
.ant-menu.cvat-actions-menu {
-webkit-box-shadow: 0 0 17px rgba(0,0,0,0.2);
box-shadow: 0 0 17px rgba(0,0,0,0.2);
}
.cvat-actions-menu > hr {
border: 0.5px solid #D6D6D6;
}
.cvat-task-item-load-submenu-item {
padding: 0px;
.cvat-actions-menu > *:hover {
background-color: rgba(24,144,255,0.05);
}
.cvat-task-item-load-submenu-item > span > .ant-upload {
width: 100%;
.cvat-actions-menu-load-submenu-item:hover {
background-color: rgba(24,144,255,0.05);
}
.cvat-task-item-dump-submenu-item {
padding: 0px;
.cvat-actions-menu-dump-submenu-item:hover {
background-color: rgba(24,144,255,0.05);
}
.cvat-actions-menu > li:nth-child(2) > div > span {
margin-right: 15px;
}
.cvat-task-item-preview {
@ -279,8 +330,12 @@
}
.cvat-task-item-preview-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
justify-content: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
overflow: hidden;
margin: 20px;
margin-top: 0px;
@ -296,8 +351,8 @@
margin-right: 5px;
}
.cvat-task-completed-progress > div > div > div > div {
background: #52c41a !important;
.cvat-task-completed-progress > div > div > div > div.ant-progress-bg {
background: #52c41a !important; /* csslint allow: important */
}
.cvat-task-progress-progress {
@ -334,6 +389,26 @@
margin-top: 20px;
}
.cvat-dataset-repository-url > a {
margin-right: 10px;
}
.cvat-dataset-repository-url > .ant-tag-red {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
opacity: 0.4;
}
.cvat-dataset-repository-url > .ant-tag-red:hover {
opacity: 0.8;
}
.cvat-dataset-repository-url > .ant-tag-red:active {
opacity: 1;
}
.cvat-task-job-list {
width: 100%;
height: auto;
@ -355,8 +430,12 @@
}
.cvat-task-preview-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
justify-content: start;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: start;
overflow: hidden;
margin-bottom: 20px;
}
@ -370,36 +449,62 @@
margin-left: 15px;
}
.cvat-raw-labels-viewer {
border-color: #d9d9d9 !important;
box-shadow: none !important;
textarea.ant-input.cvat-raw-labels-viewer {
border-color: #d9d9d9;
-webkit-box-shadow: none;
box-shadow: none;
border-top: none;
border-radius: 0px 0px 5px 5px;
min-height: 9em !important;
min-height: 9em;
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
}
.cvat-raw-labels-viewer:focus {
border-color: #d9d9d9;
-webkit-box-shadow: none;
box-shadow: none;
}
.cvat-raw-labels-viewer:hover {
border-color: #d9d9d9;
-webkit-box-shadow: none;
box-shadow: none;
}
.cvat-constructor-viewer {
border: 1px solid #d9d9d9;
box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
border-top: none;
border-radius: 0px 0px 5px 5px;
padding: 5px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
overflow-y: auto;
min-height: 9em;
}
.cvat-constructor-viewer-item {
height: -webkit-fit-content;
height: -moz-fit-content;
height: fit-content;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 2px 10px;
border-radius: 2px;
margin: 2px;
margin-left: 8px;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border: 1px solid rgba(0, 0, 0, 0);
opacity: 0.6;
}
@ -419,14 +524,23 @@
}
.cvat-constructor-viewer-new-item {
height: -webkit-fit-content;
height: -moz-fit-content;
height: fit-content;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 2px 10px;
border-radius: 2px;
margin: 2px;
margin-left: 8px;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
opacity: 1;
}
@ -466,8 +580,8 @@
.cvat-task-jobs-table > div > div {
text-align: center;
}
.cvat-task-jobs-table > div > div > ul {
float: none !important;
.cvat-task-jobs-table > div > div > .ant-table-pagination.ant-pagination {
float: none;
}
.cvat-job-completed-color {
@ -514,12 +628,167 @@
}
.cvat-share-tree {
height: -webkit-fit-content;
height: -moz-fit-content;
height: fit-content;
min-height: 10em;
max-height: 20em;
overflow: auto;
}
.cvat-models-page {
padding-top: 30px;
height: 100%;
overflow: auto;
}
.cvat-models-page > div:nth-child(1) {
margin-bottom: 10px;
}
.cvat-models-page > div:nth-child(1) > div:nth-child(1) {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.cvat-models-page > div:nth-child(1) > div:nth-child(2) {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
}
/* empty-models icon */
.cvat-empty-models-list > div:nth-child(1) {
margin-top: 50px;
}
.cvat-empty-models-list > div:nth-child(2) > div {
margin-top: 20px;
}
/* No models uploaded yet */
.cvat-empty-models-list > div:nth-child(2) > div > span {
font-size: 20px;
color: #4A4A4A;
}
/* To annotate your task automatically */
.cvat-empty-models-list > div:nth-child(3) {
margin-top: 10px;
}
.cvat-models-list {
height: 100%;
overflow-y: auto;
}
.cvat-models-list-item {
width: 100%;
height: 60px;
border: 1px solid #c3c3c3;
border-radius: 3px;
margin-bottom: 15px;
padding: 15px;
background: white;
}
.cvat-models-list-item:hover {
border: 1px solid #40a9ff;
}
.cvat-models-list-item > div {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.cvat-models-list-item > div:nth-child(2) > span {
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.cvat-models-list-item > div:nth-child(3) > span {
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.anticon.cvat-empty-models-icon > img {
width: 90px;
}
.cvat-create-model-form-wrapper {
text-align: center;
margin-top: 40px;
overflow-y: auto;
height: 90%;
}
.cvat-create-model-form-wrapper > div > span {
font-size: 36px;
}
.cvat-create-model-content {
margin-top: 20px;
width: 100%;
height: auto;
border: 1px solid #c3c3c3;
border-radius: 3px;
padding: 20px;
background: white;
text-align: initial;
}
.cvat-create-model-content > div:nth-child(1) > i {
float: right;
font-size: 20px;
color: red;
}
.cvat-create-model-content > div:nth-child(4) {
margin-top: 10px;
}
.cvat-create-model-content > div:nth-child(5) > button {
margin-top: 10px;
float: right;
width: 120px;
}
.cvat-run-model-dialog > div:not(first-child) {
margin-top: 10px;
}
.cvat-run-model-dialog-info-icon {
color: blue;
}
.cvat-run-model-dialog-remove-mapping-icon {
color: red;
}
#root {
width: 100%;
height: 100%;
min-height: 100%;
display: -ms-grid;
display: grid;
}
#cvat-create-task-button {
padding: 0 30px;
}
#cvat-create-model-button {
padding: 0 30px;
}

@ -0,0 +1,191 @@
import getCore from '../core';
const core = getCore();
const baseURL = core.config.backendAPI.slice(0, -7);
interface GitPlugin {
name: string;
description: string;
cvat: {
classes: {
Task: {
prototype: {
save: {
leave: (plugin: GitPlugin, task: any) => void;
};
};
};
};
};
data: {
task: any;
lfs: boolean;
repos: string;
};
callbacks: {
onStatusChange: ((status: string) => void) | null;
};
}
interface ReposData {
url: string;
status: {
value: 'sync' | '!sync' | 'merged';
error: string | null;
};
}
function waitForClone(cloneResponse: any): Promise<void> {
return new Promise((resolve, reject): void => {
async function checkCallback(): Promise<void> {
core.server.request(
`${baseURL}/git/repository/check/${cloneResponse.rq_id}`,
{
method: 'GET',
},
).then((response: any): void => {
if (['queued', 'started'].includes(response.status)) {
setTimeout(checkCallback, 1000);
} else if (response.status === 'finished') {
resolve();
} else if (response.status === 'failed') {
let message = 'Repository status check failed. ';
if (response.stderr) {
message += response.stderr;
}
reject(message);
} else {
const message = `Repository status check returned the status "${response.status}"`;
reject(message);
}
}).catch((error: any): void => {
const message = `Can not sent a request to clone the repository. ${error.toString()}`;
reject(message);
});
}
setTimeout(checkCallback, 1000);
});
}
async function cloneRepository(
this: any,
plugin: GitPlugin,
createdTask: any,
): Promise<void> {
return new Promise((resolve, reject): void => {
if (typeof (this.id) !== 'undefined' || plugin.data.task !== this) {
// not the first save, we do not need to clone the repository
// or anchor set for another task
resolve();
} else if (plugin.data.repos) {
if (plugin.callbacks.onStatusChange) {
plugin.callbacks.onStatusChange('The repository is being cloned..');
}
core.server.request(`${baseURL}/git/repository/create/${createdTask.id}`, {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
data: JSON.stringify({
path: plugin.data.repos,
lfs: plugin.data.lfs,
tid: createdTask.id,
}),
}).then(waitForClone).then((): void => {
resolve();
}).catch((error: any): void => {
createdTask.delete().finally((): void => {
reject(
new core.exceptions.PluginError(
typeof (error) === 'string'
? error : error.message,
),
);
});
});
}
});
}
export function registerGitPlugin(): void {
const plugin: GitPlugin = {
name: 'Git',
description: 'Plugin allows to work with git repositories',
cvat: {
classes: {
Task: {
prototype: {
save: {
leave: cloneRepository,
},
},
},
},
},
data: {
task: null,
lfs: false,
repos: '',
},
callbacks: {
onStatusChange: null,
},
};
core.plugins.register(plugin);
}
export async function getReposData(tid: number): Promise<ReposData | null> {
const response = await core.server.request(
`${baseURL}/git/repository/get/${tid}`,
{
method: 'GET',
},
);
if (!response.url.value) {
return null;
}
return {
url: response.url.value.split(/\s+/)[0],
status: {
value: response.status.value,
error: response.status.error,
},
};
}
export function syncRepos(tid: number): Promise<void> {
return new Promise((resolve, reject): void => {
core.server.request(`${baseURL}/git/repository/push/${tid}`, {
method: 'GET',
}).then((syncResponse: any): void => {
async function checkSync(): Promise<void> {
const id = syncResponse.rq_id;
const response = await core.server.request(`${baseURL}/git/repository/check/${id}`, {
method: 'GET',
});
if (['queued', 'started'].includes(response.status)) {
setTimeout(checkSync, 1000);
} else if (response.status === 'finished') {
resolve();
} else if (response.status === 'failed') {
const message = `Can not push to remote repository. Message: ${response.stderr}`;
throw new Error(message);
} else {
const message = `Check returned status "${response.status}".`;
throw new Error(message);
}
}
setTimeout(checkSync, 1000);
}).catch((error: any): void => {
reject(error);
});
});
}

@ -30,6 +30,13 @@ class PluginChecker {
}
return false;
}
case SupportedPlugins.TF_SEGMENTATION: {
const response = await fetch(`${serverHost}/tensorflow/segmentation/meta/get`);
if (response.ok) {
return true;
}
return false;
}
case SupportedPlugins.ANALYTICS: {
const response = await fetch(`${serverHost}/analytics/app/kibana`);
if (response.ok) {

@ -52,9 +52,16 @@ const validationPatterns = {
},
validateURL: {
pattern: /^(https?):\/\/[^\s$.?#].[^\s]*$/,
// eslint-disable-next-line
pattern: /^((https?:\/\/)|(git@))[^\s$.?#].[^\s]*$/, // url, ssh url, ip
message: 'URL is not valid',
},
validatePath: {
// eslint-disable-next-line
pattern: /^\[\/?([A-z0-9-_+]+\/)*([A-z0-9]+\.(xml|zip|json))\]$/,
message: 'Git path is not valid',
},
};
export default { ...validationPatterns };

@ -147,6 +147,7 @@ def get_meta_info(request):
"uploadDate": dl_model.created_date,
"updateDate": dl_model.updated_date,
"labels": labels,
"owner": dl_model.owner.id,
})
queue = django_rq.get_queue("low")

@ -433,11 +433,12 @@ def get(tid, user):
response['status']['value'] = str(db_git.status)
except git.exc.GitCommandError as ex:
_have_no_access_exception(ex)
db_git.save()
except Exception as ex:
db_git.status = GitStatusChoice.NON_SYNCED
db_git.save()
response['status']['error'] = str(ex)
db_git.save()
return response
def update_states():

Loading…
Cancel
Save