React & Antd: Dashboard migration (#892)
* Removed old dashboard * Getting all users * Updated changelog * Reimplemented login decorator * Implicit host, scheme in docker-compose * Fixed issue with pagination * Implicit page size parameter for tasks * Fixed linkedin icon, added links to tasks in notifications * Configurable method for check pluginmain
parent
dc7bfa9b12
commit
171a9202ed
@ -1,794 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global
|
|
||||||
showMessage
|
|
||||||
showOverlay
|
|
||||||
userConfirm
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.cvatUI = window.cvatUI || {};
|
|
||||||
|
|
||||||
const AutoAnnotationServer = {
|
|
||||||
start(modelId, taskId, data, success, error, progress, check) {
|
|
||||||
$.ajax({
|
|
||||||
url: `/auto_annotation/start/${modelId}/${taskId}`,
|
|
||||||
type: 'POST',
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
contentType: 'application/json',
|
|
||||||
success: (responseData) => {
|
|
||||||
check(responseData.id, success, error, progress);
|
|
||||||
},
|
|
||||||
error: (responseData) => {
|
|
||||||
const message = `Starting request has been failed. Code: ${responseData.status}. Message: ${responseData.responseText || responseData.statusText}`;
|
|
||||||
error(message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
update(data, success, error, progress, check, modelId) {
|
|
||||||
let url = '';
|
|
||||||
if (modelId === null) {
|
|
||||||
url = '/auto_annotation/create';
|
|
||||||
} else {
|
|
||||||
url = `/auto_annotation/update/${modelId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url,
|
|
||||||
type: 'POST',
|
|
||||||
data,
|
|
||||||
contentType: false,
|
|
||||||
processData: false,
|
|
||||||
success: (responseData) => {
|
|
||||||
check(responseData.id, success, error, progress);
|
|
||||||
},
|
|
||||||
error: (responseData) => {
|
|
||||||
const message = `Creating request has been failed. Code: ${responseData.status}. Message: ${responseData.responseText || responseData.statusText}`;
|
|
||||||
error(message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
delete(modelId, success, error) {
|
|
||||||
$.ajax({
|
|
||||||
url: `/auto_annotation/delete/${modelId}`,
|
|
||||||
type: 'DELETE',
|
|
||||||
success,
|
|
||||||
error: (data) => {
|
|
||||||
const message = `Deleting request has been failed. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
|
|
||||||
error(message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
check(workerId, success, error, progress) {
|
|
||||||
function updateProgress(data) {
|
|
||||||
if (data.progress && progress) {
|
|
||||||
progress(data.progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkCallback() {
|
|
||||||
$.ajax({
|
|
||||||
url: `/auto_annotation/check/${workerId}`,
|
|
||||||
type: 'GET',
|
|
||||||
success: (data) => {
|
|
||||||
updateProgress(data, progress);
|
|
||||||
|
|
||||||
switch (data.status) {
|
|
||||||
case 'failed':
|
|
||||||
error(`Checking request has returned the "${data.status}" status. Message: ${data.error}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'unknown':
|
|
||||||
error(`Checking request has returned the "${data.status}" status.`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'finished':
|
|
||||||
success();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
setTimeout(checkCallback, 1000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: (data) => {
|
|
||||||
const message = `Checking request has been failed. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
|
|
||||||
error(message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(checkCallback, 1000);
|
|
||||||
},
|
|
||||||
|
|
||||||
meta(tids, success, error) {
|
|
||||||
$.ajax({
|
|
||||||
url: '/auto_annotation/meta/get',
|
|
||||||
type: 'POST',
|
|
||||||
data: JSON.stringify(tids),
|
|
||||||
contentType: 'application/json',
|
|
||||||
success,
|
|
||||||
error: (data) => {
|
|
||||||
const message = `Getting meta request has been failed. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
|
|
||||||
error(message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
cancel(tid, success, error) {
|
|
||||||
$.ajax({
|
|
||||||
url: `/auto_annotation/cancel/${tid}`,
|
|
||||||
type: 'GET',
|
|
||||||
success,
|
|
||||||
error: (data) => {
|
|
||||||
const message = `Getting meta request has been failed. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
|
|
||||||
error(message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class AutoAnnotationModelManagerView {
|
|
||||||
constructor() {
|
|
||||||
const html = `<div class="modal hidden" id="${window.cvatUI.autoAnnotation.managerWindowId}">
|
|
||||||
<div class="modal-content" id="${window.cvatUI.autoAnnotation.managerContentId}">
|
|
||||||
<div style="float: left; width: 55%; height: 100%;">
|
|
||||||
<center>
|
|
||||||
<label class="regular h1"> Uploaded Models </label>
|
|
||||||
</center>
|
|
||||||
<div style="overflow: auto; height: 90%; margin-top: 2%;">
|
|
||||||
<table class="regular modelsTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th> Name </th>
|
|
||||||
<th> Upload Date </th>
|
|
||||||
<th> Actions </th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="${window.cvatUI.autoAnnotation.managerUploadedModelsId}"> </tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="regular" id="${window.cvatUI.autoAnnotation.uploadContentId}">
|
|
||||||
<center>
|
|
||||||
<label class="regular h1" id="${window.cvatUI.autoAnnotation.uploadTitleId}"> Create Model </label>
|
|
||||||
</center>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 25%"> <label class="regular h3"> Name: </label> </td>
|
|
||||||
<td> <input type="text" id="${window.cvatUI.autoAnnotation.uploadNameInputId}" class="regular h3" style="width: 100%"> </td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td> <label class="regular h3"> Source: </label> </td>
|
|
||||||
<td>
|
|
||||||
<input id="${window.cvatUI.autoAnnotation.uploadLocalSourceId}" type="radio" name="modelSourceType" value="local" checked>
|
|
||||||
<label for="${window.cvatUI.autoAnnotation.uploadLocalSourceId}" class="regular h3"> Local </label>
|
|
||||||
<br>
|
|
||||||
<input id="${window.cvatUI.autoAnnotation.uploadShareSourceId}" type="radio" name="modelSourceType" value="shared">
|
|
||||||
<label for="${window.cvatUI.autoAnnotation.uploadShareSourceId}" class="regular h3"> Share </label>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="${window.cvatUI.autoAnnotation.uploadGloballyBlockId}">
|
|
||||||
<td> <label class="regular h3"> Upload Globally </label> </td>
|
|
||||||
<td> <input type="checkbox" id="${window.cvatUI.autoAnnotation.uploadGloballyId}"> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div style="text-align: left;">
|
|
||||||
<div>
|
|
||||||
<button id="${window.cvatUI.autoAnnotation.selectFilesButtonId}" class="regular h3"> Select Files </button>
|
|
||||||
<label id="${window.cvatUI.autoAnnotation.selectedFilesId}" class="regular h3" style="margin-left: 10px"> No Files </label>
|
|
||||||
<input id="${window.cvatUI.autoAnnotation.localFileSelectorId}" type="file" accept=".bin,.xml,.json,.py" style="display: none" multiple>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div style="float: right; width: 50%; height: 50px;">
|
|
||||||
<button class="regular h3" id="${window.cvatUI.autoAnnotation.submitUploadButtonId}"> Submit </button>
|
|
||||||
<button class="regular h3" id="${window.cvatUI.autoAnnotation.cancelUploadButtonId}"> Cancel </button>
|
|
||||||
</div>
|
|
||||||
<div style="float: left; overflow-y: auto; height: 75px; overflow: auto; width: 100%; word-break: break-word;">
|
|
||||||
<label class="regular h3 selectable" style="float: left;" id="${window.cvatUI.autoAnnotation.uploadMessageId}"> </label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
this.el = $(html);
|
|
||||||
|
|
||||||
this.table = this.el.find(`#${window.cvatUI.autoAnnotation.managerUploadedModelsId}`);
|
|
||||||
this.globallyBlock = this.el.find(`#${window.cvatUI.autoAnnotation.uploadGloballyBlockId}`);
|
|
||||||
this.uploadTitle = this.el.find(`#${window.cvatUI.autoAnnotation.uploadTitleId}`);
|
|
||||||
this.uploadNameInput = this.el.find(`#${window.cvatUI.autoAnnotation.uploadNameInputId}`);
|
|
||||||
this.uploadMessage = this.el.find(`#${window.cvatUI.autoAnnotation.uploadMessageId}`);
|
|
||||||
this.selectedFilesLabel = this.el.find(`#${window.cvatUI.autoAnnotation.selectedFilesId}`);
|
|
||||||
this.modelNameInput = this.el.find(`#${window.cvatUI.autoAnnotation.uploadNameInputId}`);
|
|
||||||
this.localSource = this.el.find(`#${window.cvatUI.autoAnnotation.uploadLocalSourceId}`);
|
|
||||||
this.shareSource = this.el.find(`#${window.cvatUI.autoAnnotation.uploadShareSourceId}`);
|
|
||||||
this.cancelButton = this.el.find(`#${window.cvatUI.autoAnnotation.cancelUploadButtonId}`);
|
|
||||||
this.submitButton = this.el.find(`#${window.cvatUI.autoAnnotation.submitUploadButtonId}`);
|
|
||||||
this.globallyBox = this.el.find(`#${window.cvatUI.autoAnnotation.uploadGloballyId}`);
|
|
||||||
this.selectButton = this.el.find(`#${window.cvatUI.autoAnnotation.selectFilesButtonId}`);
|
|
||||||
this.localSelector = this.el.find(`#${window.cvatUI.autoAnnotation.localFileSelectorId}`);
|
|
||||||
this.shareSelector = $('#dashboardShareBrowseModal');
|
|
||||||
this.shareBrowseTree = $('#dashboardShareBrowser');
|
|
||||||
this.submitShare = $('#dashboardSubmitBrowseServer');
|
|
||||||
|
|
||||||
this.id = null;
|
|
||||||
this.source = this.localSource.prop('checked') ? 'local' : 'shared';
|
|
||||||
this.files = [];
|
|
||||||
|
|
||||||
function filesLabel(source, files) {
|
|
||||||
const fileLabels = source === 'local' ? [...files].map(el => el.name) : files;
|
|
||||||
if (fileLabels.length) {
|
|
||||||
const labelStr = fileLabels.join(', ');
|
|
||||||
if (labelStr.length > 30) {
|
|
||||||
return `${labelStr.substr(0, 30)}..`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return labelStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'No Files';
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractFiles(extensions, files, source) {
|
|
||||||
const extractedFiles = {};
|
|
||||||
function getExt(file) {
|
|
||||||
return source === 'local' ? file.name.split('.').pop() : file.split('.').pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFile(file, extention) {
|
|
||||||
if (extention in files) {
|
|
||||||
throw Error(`More than one file with the extension .${extention} have been found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
extractedFiles[extention] = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
files.forEach((file) => {
|
|
||||||
const fileExt = getExt(file);
|
|
||||||
if (extensions.includes(fileExt)) {
|
|
||||||
addFile(file, fileExt);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return extractedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateFiles(isUpdate, files, source) {
|
|
||||||
const extensions = ['xml', 'bin', 'py', 'json'];
|
|
||||||
const extractedFiles = extractFiles(extensions, files, source);
|
|
||||||
|
|
||||||
if (!isUpdate) {
|
|
||||||
extensions.forEach((extension) => {
|
|
||||||
if (!(extension in extractedFiles)) {
|
|
||||||
throw Error(`Please specify a .${extension} file`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return extractedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.localSource.on('click', () => {
|
|
||||||
if (this.source !== 'local') {
|
|
||||||
this.source = 'local';
|
|
||||||
this.files = [];
|
|
||||||
this.selectedFilesLabel.text(filesLabel(this.source, this.files));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.shareSource.on('click', () => {
|
|
||||||
if (this.source !== 'shared') {
|
|
||||||
this.source = 'shared';
|
|
||||||
this.files = [];
|
|
||||||
this.selectedFilesLabel.text(filesLabel(this.source, this.files));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.selectButton.on('click', () => {
|
|
||||||
if (this.source === 'local') {
|
|
||||||
this.localSelector.click();
|
|
||||||
} else {
|
|
||||||
this.shareSelector.appendTo('body');
|
|
||||||
this.shareBrowseTree.jstree('refresh');
|
|
||||||
this.shareSelector.removeClass('hidden');
|
|
||||||
this.shareBrowseTree.jstree({
|
|
||||||
core: {
|
|
||||||
async data(obj, callback) {
|
|
||||||
const directory = obj.id === '#' ? '' : `${obj.id}/`;
|
|
||||||
|
|
||||||
let shareFiles = await window.cvat.server.share(directory);
|
|
||||||
shareFiles = Array.from(shareFiles, (element) => {
|
|
||||||
const shareFileInfo = {
|
|
||||||
id: `${directory}${element.name}`,
|
|
||||||
children: element.type === 'DIR',
|
|
||||||
text: element.name,
|
|
||||||
icon: element.type === 'DIR' ? 'jstree-folder' : 'jstree-file',
|
|
||||||
};
|
|
||||||
|
|
||||||
return shareFileInfo;
|
|
||||||
});
|
|
||||||
|
|
||||||
callback.call(this, shareFiles);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: ['checkbox', 'sort'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.submitShare.on('click', () => {
|
|
||||||
if (!this.el.hasClass('hidden')) {
|
|
||||||
this.shareSelector.addClass('hidden');
|
|
||||||
this.files = this.shareBrowseTree.jstree(true).get_selected();
|
|
||||||
this.selectedFilesLabel.text(filesLabel(this.source, this.files));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.localSelector.on('change', (e) => {
|
|
||||||
this.files = Array.from(e.target.files);
|
|
||||||
this.selectedFilesLabel.text(filesLabel(this.source, this.files));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.cancelButton.on('click', () => this.el.addClass('hidden'));
|
|
||||||
this.submitButton.on('click', () => {
|
|
||||||
try {
|
|
||||||
this.submitButton.prop('disabled', true);
|
|
||||||
|
|
||||||
const name = $.trim(this.modelNameInput.prop('value'));
|
|
||||||
if (!name.length) {
|
|
||||||
this.uploadMessage.css('color', 'red');
|
|
||||||
this.uploadMessage.text('Please specify a model name');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let validatedFiles = {};
|
|
||||||
try {
|
|
||||||
validatedFiles = validateFiles(this.id !== null, this.files, this.source);
|
|
||||||
} catch (err) {
|
|
||||||
this.uploadMessage.css('color', 'red');
|
|
||||||
this.uploadMessage.text(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelData = new FormData();
|
|
||||||
modelData.append('name', name);
|
|
||||||
modelData.append('storage', this.source);
|
|
||||||
modelData.append('shared', this.globallyBox.prop('checked'));
|
|
||||||
|
|
||||||
['xml', 'bin', 'json', 'py'].filter(e => e in validatedFiles).forEach((ext) => {
|
|
||||||
modelData.append(ext, validatedFiles[ext]);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.uploadMessage.text('');
|
|
||||||
const overlay = showOverlay('Send request to the server..');
|
|
||||||
window.cvatUI.autoAnnotation.server.update(modelData, () => {
|
|
||||||
window.location.reload();
|
|
||||||
}, (message) => {
|
|
||||||
overlay.remove();
|
|
||||||
showMessage(message);
|
|
||||||
}, (progress) => {
|
|
||||||
overlay.setMessage(progress);
|
|
||||||
}, window.cvatUI.autoAnnotation.server.check, this.id);
|
|
||||||
} finally {
|
|
||||||
this.submitButton.prop('disabled', false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
const setBlocked = () => {
|
|
||||||
if (window.cvatUI.autoAnnotation.data.admin) {
|
|
||||||
this.globallyBlock.removeClass('hidden');
|
|
||||||
} else {
|
|
||||||
this.globallyBlock.addClass('hidden');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setBlocked();
|
|
||||||
this.uploadTitle.text('Create Model');
|
|
||||||
this.uploadNameInput.prop('value', '');
|
|
||||||
this.uploadMessage.css('color', '');
|
|
||||||
this.uploadMessage.text('');
|
|
||||||
this.selectedFilesLabel.text('No Files');
|
|
||||||
this.localSource.prop('checked', true);
|
|
||||||
this.globallyBox.prop('checked', false);
|
|
||||||
this.table.empty();
|
|
||||||
|
|
||||||
this.id = null;
|
|
||||||
this.source = this.localSource.prop('checked') ? 'local' : 'share';
|
|
||||||
this.files = [];
|
|
||||||
|
|
||||||
const updateButtonClickHandler = (event) => {
|
|
||||||
this.reset();
|
|
||||||
|
|
||||||
this.uploadTitle.text('Update Model');
|
|
||||||
this.uploadNameInput.prop('value', `${event.data.model.name}`);
|
|
||||||
this.id = event.data.model.id;
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteButtonClickHandler = (event) => {
|
|
||||||
userConfirm(`Do you actually want to delete the "${event.data.model.name}" model. Are you sure?`, () => {
|
|
||||||
window.cvatUI.autoAnnotation.server.delete(event.data.model.id, () => {
|
|
||||||
const filtered = window.cvatUI.autoAnnotation.data.models.filter(
|
|
||||||
item => item !== event.data.model,
|
|
||||||
);
|
|
||||||
window.cvatUI.autoAnnotation.data.models = filtered;
|
|
||||||
this.reset();
|
|
||||||
}, (message) => {
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getModelModifyButtons = (model) => {
|
|
||||||
if (model.primary) {
|
|
||||||
return '<td> <label class="h1 regular"> Primary Model </label> </td>';
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateButtonHtml = '<button class="regular h3" style="width: 7em;"> Update </button>';
|
|
||||||
const deleteButtonHtml = '<button class="regular h3" style="width: 7em; margin-top: 5%;"> Delete </button>';
|
|
||||||
|
|
||||||
return $('<td> </td>').append(
|
|
||||||
$(updateButtonHtml).on('click', { model }, updateButtonClickHandler),
|
|
||||||
$(deleteButtonHtml).on('click', { model }, deleteButtonClickHandler),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.cvatUI.autoAnnotation.data.models.forEach((model) => {
|
|
||||||
const rowHtml = `<tr>
|
|
||||||
<td> ${model.name} </td>
|
|
||||||
<td> ${model.uploadDate} </td>
|
|
||||||
</tr>`;
|
|
||||||
|
|
||||||
this.table.append(
|
|
||||||
$(rowHtml).append(getModelModifyButtons(model)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
show() {
|
|
||||||
this.el.removeClass('hidden');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
get element() {
|
|
||||||
return this.el;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AutoAnnotationModelRunnerView {
|
|
||||||
constructor() {
|
|
||||||
const html = `<div class="modal hidden" id="${window.cvatUI.autoAnnotation.runnerWindowId}">
|
|
||||||
<div class="modal-content" id="${window.cvatUI.autoAnnotation.runnerContentId}">
|
|
||||||
<div style="width: 55%; height: 100%; float: left;">
|
|
||||||
<center style="height: 10%;">
|
|
||||||
<label class="regular h1"> Uploaded Models </label>
|
|
||||||
</center>
|
|
||||||
<div style="height: 70%; overflow: auto; margin-top: 2%;">
|
|
||||||
<table class="modelsTable" id="${window.cvatUI.autoAnnotation.runnerUploadedModelsId}"> </table>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input type="checkbox" id="${window.cvatUI.autoAnnotation.removeCurrentAnnotationId}"/>
|
|
||||||
<label class="regular h3" for="${window.cvatUI.autoAnnotation.removeCurrentAnnotationId}"> Remove current annotation </label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="width: 40%; height: 100%; float: left; margin-left: 3%;">
|
|
||||||
<center style="height: 10%;">
|
|
||||||
<label class="regular h1"> Annotation Labels </label>
|
|
||||||
</center>
|
|
||||||
<div style="height: 70%; overflow: auto; margin-top: 2%;">
|
|
||||||
<table class="regular" style="text-align: center; word-break: break-all; width: 100%;">
|
|
||||||
<thead>
|
|
||||||
<tr style="width: 100%;">
|
|
||||||
<th style="width: 45%;"> Task Label </th>
|
|
||||||
<th style="width: 45%;"> DL Model Label </th>
|
|
||||||
<th style="width: 10%;"> </th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="${window.cvatUI.autoAnnotation.annotationLabelsId}">
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div style="float:right;">
|
|
||||||
<button class="regular h3" style="width: 6em;" id="${window.cvatUI.autoAnnotation.submitAnnotationId}"> Start </button>
|
|
||||||
<button class="regular h3" style="width: 6em;" id="${window.cvatUI.autoAnnotation.cancelAnnotationId}"> Cancel </button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
this.el = $(html);
|
|
||||||
this.id = null;
|
|
||||||
this.tid = null;
|
|
||||||
this.initButton = null;
|
|
||||||
this.modelsTable = this.el.find(`#${window.cvatUI.autoAnnotation.runnerUploadedModelsId}`);
|
|
||||||
this.labelsTable = this.el.find(`#${window.cvatUI.autoAnnotation.annotationLabelsId}`);
|
|
||||||
this.active = null;
|
|
||||||
|
|
||||||
this.el.find(`#${window.cvatUI.autoAnnotation.cancelAnnotationId}`).on('click', () => {
|
|
||||||
this.el.addClass('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.el.find(`#${window.cvatUI.autoAnnotation.submitAnnotationId}`).on('click', () => {
|
|
||||||
try {
|
|
||||||
if (this.id === null) {
|
|
||||||
throw Error('Please specify a model for an annotation process');
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapping = {};
|
|
||||||
$('.annotatorMappingRow').each((_, element) => {
|
|
||||||
const dlModelLabel = $(element).find('.annotatorDlLabelSelector')[0].value;
|
|
||||||
const taskLabel = $(element).find('.annotatorTaskLabelSelector')[0].value;
|
|
||||||
if (dlModelLabel in mapping) {
|
|
||||||
throw Error(`The label "${dlModelLabel}" has been specified twice or more`);
|
|
||||||
}
|
|
||||||
mapping[dlModelLabel] = taskLabel;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!Object.keys(mapping).length) {
|
|
||||||
throw Error('Labels for an annotation process haven\'t been found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const overlay = showOverlay('Request has been sent');
|
|
||||||
window.cvatUI.autoAnnotation.server.start(this.id, this.tid, {
|
|
||||||
reset: $(`#${window.cvatUI.autoAnnotation.removeCurrentAnnotationId}`).prop('checked'),
|
|
||||||
labels: mapping,
|
|
||||||
}, () => {
|
|
||||||
overlay.remove();
|
|
||||||
this.initButton[0].setupRun();
|
|
||||||
window.cvatUI.autoAnnotation.runner.hide();
|
|
||||||
}, (message) => {
|
|
||||||
overlay.remove();
|
|
||||||
this.initButton[0].setupRun();
|
|
||||||
showMessage(message);
|
|
||||||
}, () => {
|
|
||||||
window.location.reload();
|
|
||||||
}, window.cvatUI.autoAnnotation.server.check);
|
|
||||||
} catch (error) {
|
|
||||||
showMessage(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(data, initButton) {
|
|
||||||
function labelsSelect(labels, elClass) {
|
|
||||||
const select = $(`<select class="regular h3 ${elClass}" style="width:100%;"> </select>`);
|
|
||||||
labels.forEach(label => select.append($(`<option value="${label}"> ${label} </option>`)));
|
|
||||||
select.prop('value', null);
|
|
||||||
|
|
||||||
return select;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeCreator(dlSelect, taskSelect, callback) {
|
|
||||||
let dlIsFilled = false;
|
|
||||||
let taskIsFilled = false;
|
|
||||||
const creator = $('<tr style="margin-bottom: 5px;"> </tr>').append(
|
|
||||||
$('<td style="width: 45%;"> </td>').append(taskSelect),
|
|
||||||
$('<td style="width: 45%;"> </td>').append(dlSelect),
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSelectHandler = () => {
|
|
||||||
$('<td style="width: 10%; position: relative;"> </td>').append(
|
|
||||||
$('<a class="close"></a>').css('top', '0px').on('click', (e) => {
|
|
||||||
$(e.target.parentNode.parentNode).remove();
|
|
||||||
}),
|
|
||||||
).appendTo(creator);
|
|
||||||
|
|
||||||
creator.addClass('annotatorMappingRow');
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
|
|
||||||
dlSelect.on('change', (e) => {
|
|
||||||
if (e.target.value && taskIsFilled) {
|
|
||||||
dlSelect.off('change');
|
|
||||||
taskSelect.off('change');
|
|
||||||
onSelectHandler();
|
|
||||||
}
|
|
||||||
dlIsFilled = Boolean(e.target.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
taskSelect.on('change', (e) => {
|
|
||||||
if (e.target.value && dlIsFilled) {
|
|
||||||
dlSelect.off('change');
|
|
||||||
taskSelect.off('change');
|
|
||||||
onSelectHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
taskIsFilled = Boolean(e.target.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return creator;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.id = null;
|
|
||||||
this.initButton = initButton;
|
|
||||||
this.tid = data.id;
|
|
||||||
this.modelsTable.empty();
|
|
||||||
this.labelsTable.empty();
|
|
||||||
this.active = null;
|
|
||||||
|
|
||||||
const modelItemClickHandler = (event) => {
|
|
||||||
if (this.active) {
|
|
||||||
this.active.style.color = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.id = event.data.model.id;
|
|
||||||
this.active = event.target;
|
|
||||||
this.active.style.color = 'darkblue';
|
|
||||||
|
|
||||||
this.labelsTable.empty();
|
|
||||||
const labels = event.data.data.labels.map(x => x.name);
|
|
||||||
const intersection = labels.filter(el => event.data.model.labels.indexOf(el) !== -1);
|
|
||||||
intersection.forEach((label) => {
|
|
||||||
const dlSelect = labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector');
|
|
||||||
dlSelect.prop('value', label);
|
|
||||||
const taskSelect = labelsSelect(labels, 'annotatorTaskLabelSelector');
|
|
||||||
taskSelect.prop('value', label);
|
|
||||||
$('<tr class="annotatorMappingRow" style="margin-bottom: 5px;"> </tr>').append(
|
|
||||||
$('<td style="width: 45%;"> </td>').append(taskSelect),
|
|
||||||
$('<td style="width: 45%;"> </td>').append(dlSelect),
|
|
||||||
$('<td style="width: 10%; position: relative;"> </td>').append(
|
|
||||||
$('<a class="close"></a>').css('top', '0px').on('click', (e) => {
|
|
||||||
$(e.target.parentNode.parentNode).remove();
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
).appendTo(this.labelsTable);
|
|
||||||
});
|
|
||||||
|
|
||||||
const dlSelect = labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector');
|
|
||||||
const taskSelect = labelsSelect(labels, 'annotatorTaskLabelSelector');
|
|
||||||
|
|
||||||
const callback = () => {
|
|
||||||
makeCreator(
|
|
||||||
labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector'),
|
|
||||||
labelsSelect(labels, 'annotatorTaskLabelSelector'),
|
|
||||||
callback,
|
|
||||||
).appendTo(this.labelsTable);
|
|
||||||
};
|
|
||||||
|
|
||||||
makeCreator(dlSelect, taskSelect, callback).appendTo(this.labelsTable);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.cvatUI.autoAnnotation.data.models.forEach((model) => {
|
|
||||||
this.modelsTable.append(
|
|
||||||
$(`<tr> <td> <label class="regular h3"> ${model.name} (${model.uploadDate}) </label> </td> </tr>`).on(
|
|
||||||
'click', { model, data }, modelItemClickHandler,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
show() {
|
|
||||||
this.el.removeClass('hidden');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
hide() {
|
|
||||||
this.el.addClass('hidden');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
get element() {
|
|
||||||
return this.el;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.cvatUI.autoAnnotation = {
|
|
||||||
managerWindowId: 'annotatorManagerWindow',
|
|
||||||
managerContentId: 'annotatorManagerContent',
|
|
||||||
managerUploadedModelsId: 'annotatorManagerUploadedModels',
|
|
||||||
uploadContentId: 'annotatorManagerUploadModel',
|
|
||||||
uploadTitleId: 'annotatorManagerUploadTitle',
|
|
||||||
uploadNameInputId: 'annotatorManagerUploadNameInput',
|
|
||||||
uploadLocalSourceId: 'annotatorManagerUploadLocalSource',
|
|
||||||
uploadShareSourceId: 'annotatorManagerUploadShareSource',
|
|
||||||
uploadGloballyId: 'annotatorManagerUploadGlobally',
|
|
||||||
uploadGloballyBlockId: 'annotatorManagerUploadGloballyblock',
|
|
||||||
selectFilesButtonId: 'annotatorManagerUploadSelector',
|
|
||||||
selectedFilesId: 'annotatorManagerUploadSelectedFiles',
|
|
||||||
localFileSelectorId: 'annotatorManagerUploadLocalSelector',
|
|
||||||
shareFileSelectorId: 'annotatorManagerUploadShareSelector',
|
|
||||||
submitUploadButtonId: 'annotatorManagerSubmitUploadButton',
|
|
||||||
cancelUploadButtonId: 'annotatorManagerCancelUploadButton',
|
|
||||||
uploadMessageId: 'annotatorUploadStatusMessage',
|
|
||||||
|
|
||||||
runnerWindowId: 'annotatorRunnerWindow',
|
|
||||||
runnerContentId: 'annotatorRunnerContent',
|
|
||||||
runnerUploadedModelsId: 'annotatorRunnerUploadedModels',
|
|
||||||
removeCurrentAnnotationId: 'annotatorRunnerRemoveCurrentAnnotationBox',
|
|
||||||
annotationLabelsId: 'annotatorRunnerAnnotationLabels',
|
|
||||||
submitAnnotationId: 'annotatorRunnerSubmitAnnotationButton',
|
|
||||||
cancelAnnotationId: 'annotatorRunnerCancelAnnotationButton',
|
|
||||||
|
|
||||||
managerButtonId: 'annotatorManagerButton',
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
window.cvatUI.autoAnnotation.server = AutoAnnotationServer;
|
|
||||||
window.cvatUI.autoAnnotation.manager = new AutoAnnotationModelManagerView();
|
|
||||||
window.cvatUI.autoAnnotation.runner = new AutoAnnotationModelRunnerView();
|
|
||||||
|
|
||||||
$('body').append(window.cvatUI.autoAnnotation.manager.element, window.cvatUI.autoAnnotation.runner.element);
|
|
||||||
$(`<button id="${window.cvatUI.autoAnnotation.managerButtonId}" class="regular h1" style=""> Model Manager</button>`)
|
|
||||||
.on('click', () => {
|
|
||||||
const overlay = showOverlay('The manager are being setup..');
|
|
||||||
window.cvatUI.autoAnnotation.manager.reset().show();
|
|
||||||
overlay.remove();
|
|
||||||
}).appendTo('#dashboardManageButtons');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('dashboardReady', (event) => {
|
|
||||||
const elements = $('.dashboardItem');
|
|
||||||
const tids = Array.from(elements, el => +el.getAttribute('tid'));
|
|
||||||
|
|
||||||
window.cvatUI.autoAnnotation.server.meta(tids, (data) => {
|
|
||||||
window.cvatUI.autoAnnotation.data = data;
|
|
||||||
|
|
||||||
elements.each(function setupDashboardItem() {
|
|
||||||
const elem = $(this);
|
|
||||||
const tid = +elem.attr('tid');
|
|
||||||
|
|
||||||
const button = $('<button> Run Auto Annotation </button>').addClass('regular dashboardButtonUI');
|
|
||||||
button[0].setupRun = function setupRun() {
|
|
||||||
const self = $(this);
|
|
||||||
const taskInfo = event.detail.filter(task => task.id === tid)[0];
|
|
||||||
self.text('Run Auto Annotation').off('click').on('click', () => {
|
|
||||||
window.cvatUI.autoAnnotation.runner.reset(taskInfo, self).show();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
button[0].setupCancel = function setupCancel() {
|
|
||||||
const self = $(this);
|
|
||||||
self.off('click').text('Cancel Auto Annotation').on('click', () => {
|
|
||||||
userConfirm('Process will be canceled. Are you sure?', () => {
|
|
||||||
window.cvatUI.autoAnnotation.server.cancel(tid, () => {
|
|
||||||
this.setupRun();
|
|
||||||
}, (message) => {
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
window.cvatUI.autoAnnotation.server.check(
|
|
||||||
window.cvatUI.autoAnnotation.data.run[tid].rq_id,
|
|
||||||
() => {
|
|
||||||
this.setupRun();
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
button[0].setupRun();
|
|
||||||
button.text('Annotation has failed');
|
|
||||||
button.title(error);
|
|
||||||
},
|
|
||||||
(progress) => {
|
|
||||||
button.text(`Cancel Auto Annotation (${progress.toString().slice(0, 4)})%`);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const taskStatus = window.cvatUI.autoAnnotation.data.run[tid];
|
|
||||||
if (taskStatus && ['queued', 'started'].includes(taskStatus.status)) {
|
|
||||||
button[0].setupCancel();
|
|
||||||
} else {
|
|
||||||
button[0].setupRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
button.appendTo(elem.find('div.dashboardButtonsUI')[0]);
|
|
||||||
});
|
|
||||||
}, (error) => {
|
|
||||||
showMessage(`Cannot get models meta information: ${error}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
#annotatorManagerContent, #annotatorRunnerContent {
|
|
||||||
width: 800px;
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#annotatorManagerButton {
|
|
||||||
padding: 7px;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable {
|
|
||||||
width: 100%;
|
|
||||||
color:#666;
|
|
||||||
text-shadow: 1px 1px 0px #fff;
|
|
||||||
background:#D2D3D4;
|
|
||||||
border:#ccc 1px solid;
|
|
||||||
border-radius: 3px;
|
|
||||||
box-shadow: 0 1px 2px black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable th {
|
|
||||||
border-top: 1px solid #fafafa;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
background: #ededed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable th:first-child {
|
|
||||||
text-align: left;
|
|
||||||
padding-left:20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable tr:first-child th:first-child {
|
|
||||||
border-top-left-radius:3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable tr:first-child th:last-child {
|
|
||||||
border-top-right-radius:3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable tr {
|
|
||||||
text-align: center;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable td:first-child {
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 20px;
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable td {
|
|
||||||
padding: 18px;
|
|
||||||
border-top: 1px solid #ffffff;
|
|
||||||
border-bottom:1px solid #e0e0e0;
|
|
||||||
border-left: 1px solid #e0e0e0;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable tr.even td {
|
|
||||||
background: #f6f6f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable tr:last-child td {
|
|
||||||
border-bottom:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable tr:last-child td:first-child {
|
|
||||||
border-bottom-left-radius:3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable tr:last-child td:last-child {
|
|
||||||
border-bottom-right-radius:3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modelsTable tr:hover td {
|
|
||||||
background: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#annotatorManagerUploadModel {
|
|
||||||
float: left;
|
|
||||||
padding-left: 3%;
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global
|
|
||||||
userConfirm:false
|
|
||||||
showMessage:false
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.addEventListener('dashboardReady', () => {
|
|
||||||
function checkProcess(tid, button) {
|
|
||||||
function checkCallback() {
|
|
||||||
$.get(`/tensorflow/segmentation/check/task/${tid}`).done((statusData) => {
|
|
||||||
if (['started', 'queued'].includes(statusData.status)) {
|
|
||||||
const progress = Math.round(statusData.progress) || '0';
|
|
||||||
button.text(`Cancel Auto Segmentation (${progress}%)`);
|
|
||||||
setTimeout(checkCallback, 5000);
|
|
||||||
} else {
|
|
||||||
button.text('Run Auto Segmentation');
|
|
||||||
button.removeClass('tfAnnotationProcess');
|
|
||||||
button.prop('disabled', false);
|
|
||||||
|
|
||||||
if (statusData.status === 'failed') {
|
|
||||||
const message = `Tensorflow Segmentation failed. Error: ${statusData.stderr}`;
|
|
||||||
showMessage(message);
|
|
||||||
} else if (statusData.status !== 'finished') {
|
|
||||||
const message = `Tensorflow segmentation check request returned status "${statusData.status}"`;
|
|
||||||
showMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not sent tensorflow segmentation check request. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(checkCallback, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function runProcess(tid, button) {
|
|
||||||
$.get(`/tensorflow/segmentation/create/task/${tid}`).done(() => {
|
|
||||||
showMessage('Process has started');
|
|
||||||
button.text('Cancel Auto Segmentation (0%)');
|
|
||||||
button.addClass('tfAnnotationProcess');
|
|
||||||
checkProcess(tid, button);
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not run Auto Segmentation. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function cancelProcess(tid, button) {
|
|
||||||
$.get(`/tensorflow/segmentation/cancel/task/${tid}`).done(() => {
|
|
||||||
button.prop('disabled', true);
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not cancel Auto Segmentation. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function setupDashboardItem(item, metaData) {
|
|
||||||
const tid = +item.attr('tid');
|
|
||||||
const button = $('<button> Run Auto Segmentation </button>');
|
|
||||||
|
|
||||||
button.on('click', () => {
|
|
||||||
if (button.hasClass('tfAnnotationProcess')) {
|
|
||||||
userConfirm('The process will be canceled. Continue?', () => {
|
|
||||||
cancelProcess(tid, button);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
userConfirm('The current annotation will be lost. Are you sure?', () => {
|
|
||||||
runProcess(tid, button);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
button.addClass('dashboardTFAnnotationButton regular dashboardButtonUI');
|
|
||||||
button.appendTo(item.find('div.dashboardButtonsUI'));
|
|
||||||
|
|
||||||
if ((tid in metaData) && (metaData[tid].active)) {
|
|
||||||
button.text('Cancel Auto Segmentation');
|
|
||||||
button.addClass('tfAnnotationProcess');
|
|
||||||
checkProcess(tid, button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = $('.dashboardItem');
|
|
||||||
const tids = Array.from(elements, el => +el.getAttribute('tid'));
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: '/tensorflow/segmentation/meta/get',
|
|
||||||
data: JSON.stringify(tids),
|
|
||||||
contentType: 'application/json; charset=utf-8',
|
|
||||||
}).done((metaData) => {
|
|
||||||
elements.each(function setupDashboardItemWrapper() {
|
|
||||||
setupDashboardItem($(this), metaData);
|
|
||||||
});
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not get Auto Segmentation meta info. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
# Copyright (C) 2018 Intel Corporation
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
from cvat.settings.base import JS_3RDPARTY
|
|
||||||
|
|
||||||
JS_3RDPARTY['engine'] = JS_3RDPARTY.get('engine', []) + ['dashboard/js/enginePlugin.js']
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
# Copyright (C) 2018 Intel Corporation
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
class DashboardConfig(AppConfig):
|
|
||||||
name = 'cvat.apps.dashboard'
|
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
# plugin registration
|
|
||||||
pass
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
# Copyright (C) 2018 Intel Corporation
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,107 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
|
|
||||||
*/
|
|
||||||
/*!
|
|
||||||
* Bootstrap v3.4.1 (https://getbootstrap.com/)
|
|
||||||
* Copyright 2011-2019 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
||||||
*/
|
|
||||||
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
display: inline-block;
|
|
||||||
padding-left: 0;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.pagination > li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
.pagination > li > a,
|
|
||||||
.pagination > li > span {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
padding: 6px 12px;
|
|
||||||
margin-left: -1px;
|
|
||||||
line-height: 1.42857143;
|
|
||||||
color: #337ab7;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: 1px solid #dddddd;
|
|
||||||
}
|
|
||||||
.pagination > li > a:hover,
|
|
||||||
.pagination > li > span:hover,
|
|
||||||
.pagination > li > a:focus,
|
|
||||||
.pagination > li > span:focus {
|
|
||||||
z-index: 2;
|
|
||||||
color: #23527c;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border-color: #dddddd;
|
|
||||||
}
|
|
||||||
.pagination > li:first-child > a,
|
|
||||||
.pagination > li:first-child > span {
|
|
||||||
margin-left: 0;
|
|
||||||
border-top-left-radius: 4px;
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
}
|
|
||||||
.pagination > li:last-child > a,
|
|
||||||
.pagination > li:last-child > span {
|
|
||||||
border-top-right-radius: 4px;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
}
|
|
||||||
.pagination > .active > a,
|
|
||||||
.pagination > .active > span,
|
|
||||||
.pagination > .active > a:hover,
|
|
||||||
.pagination > .active > span:hover,
|
|
||||||
.pagination > .active > a:focus,
|
|
||||||
.pagination > .active > span:focus {
|
|
||||||
z-index: 3;
|
|
||||||
color: #ffffff;
|
|
||||||
cursor: default;
|
|
||||||
background-color: #337ab7;
|
|
||||||
border-color: #337ab7;
|
|
||||||
}
|
|
||||||
.pagination > .disabled > span,
|
|
||||||
.pagination > .disabled > span:hover,
|
|
||||||
.pagination > .disabled > span:focus,
|
|
||||||
.pagination > .disabled > a,
|
|
||||||
.pagination > .disabled > a:hover,
|
|
||||||
.pagination > .disabled > a:focus {
|
|
||||||
color: #777777;
|
|
||||||
cursor: not-allowed;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-color: #dddddd;
|
|
||||||
}
|
|
||||||
.pagination-lg > li > a,
|
|
||||||
.pagination-lg > li > span {
|
|
||||||
padding: 10px 16px;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 1.3333333;
|
|
||||||
}
|
|
||||||
.pagination-lg > li:first-child > a,
|
|
||||||
.pagination-lg > li:first-child > span {
|
|
||||||
border-top-left-radius: 6px;
|
|
||||||
border-bottom-left-radius: 6px;
|
|
||||||
}
|
|
||||||
.pagination-lg > li:last-child > a,
|
|
||||||
.pagination-lg > li:last-child > span {
|
|
||||||
border-top-right-radius: 6px;
|
|
||||||
border-bottom-right-radius: 6px;
|
|
||||||
}
|
|
||||||
.pagination-sm > li > a,
|
|
||||||
.pagination-sm > li > span {
|
|
||||||
padding: 5px 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
.pagination-sm > li:first-child > a,
|
|
||||||
.pagination-sm > li:first-child > span {
|
|
||||||
border-top-left-radius: 3px;
|
|
||||||
border-bottom-left-radius: 3px;
|
|
||||||
}
|
|
||||||
.pagination-sm > li:last-child > a,
|
|
||||||
.pagination-sm > li:last-child > span {
|
|
||||||
border-top-right-radius: 3px;
|
|
||||||
border-bottom-right-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,364 +0,0 @@
|
|||||||
/*!
|
|
||||||
* jQuery pagination plugin v1.4.2
|
|
||||||
* http://josecebe.github.io/twbs-pagination/
|
|
||||||
*
|
|
||||||
* Copyright 2014-2018, Eugene Simakin
|
|
||||||
* Released under Apache 2.0 license
|
|
||||||
* http://apache.org/licenses/LICENSE-2.0.html
|
|
||||||
*/
|
|
||||||
(function ($, window, document, undefined) {
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var old = $.fn.twbsPagination;
|
|
||||||
|
|
||||||
// PROTOTYPE AND CONSTRUCTOR
|
|
||||||
|
|
||||||
var TwbsPagination = function (element, options) {
|
|
||||||
this.$element = $(element);
|
|
||||||
this.options = $.extend({}, $.fn.twbsPagination.defaults, options);
|
|
||||||
|
|
||||||
if (this.options.startPage < 1 || this.options.startPage > this.options.totalPages) {
|
|
||||||
throw new Error('Start page option is incorrect');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.options.totalPages = parseInt(this.options.totalPages);
|
|
||||||
if (isNaN(this.options.totalPages)) {
|
|
||||||
throw new Error('Total pages option is not correct!');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.options.visiblePages = parseInt(this.options.visiblePages);
|
|
||||||
if (isNaN(this.options.visiblePages)) {
|
|
||||||
throw new Error('Visible pages option is not correct!');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.beforePageClick instanceof Function) {
|
|
||||||
this.$element.first().on('beforePage', this.options.beforePageClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.onPageClick instanceof Function) {
|
|
||||||
this.$element.first().on('page', this.options.onPageClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide if only one page exists
|
|
||||||
if (this.options.hideOnlyOnePage && this.options.totalPages == 1) {
|
|
||||||
if (this.options.initiateStartPageClick) {
|
|
||||||
this.$element.trigger('page', 1);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.href) {
|
|
||||||
this.options.startPage = this.getPageFromQueryString();
|
|
||||||
if (!this.options.startPage) {
|
|
||||||
this.options.startPage = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tagName = (typeof this.$element.prop === 'function') ?
|
|
||||||
this.$element.prop('tagName') : this.$element.attr('tagName');
|
|
||||||
|
|
||||||
if (tagName === 'UL') {
|
|
||||||
this.$listContainer = this.$element;
|
|
||||||
} else {
|
|
||||||
var elements = this.$element;
|
|
||||||
var $newListContainer = $([]);
|
|
||||||
elements.each(function(index) {
|
|
||||||
var $newElem = $("<ul></ul>");
|
|
||||||
$(this).append($newElem);
|
|
||||||
$newListContainer.push($newElem[0]);
|
|
||||||
});
|
|
||||||
this.$listContainer = $newListContainer;
|
|
||||||
this.$element = $newListContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$listContainer.addClass(this.options.paginationClass);
|
|
||||||
|
|
||||||
if (this.options.initiateStartPageClick) {
|
|
||||||
this.show(this.options.startPage);
|
|
||||||
} else {
|
|
||||||
this.currentPage = this.options.startPage;
|
|
||||||
this.render(this.getPages(this.options.startPage));
|
|
||||||
this.setupEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
TwbsPagination.prototype = {
|
|
||||||
|
|
||||||
constructor: TwbsPagination,
|
|
||||||
|
|
||||||
destroy: function () {
|
|
||||||
this.$element.empty();
|
|
||||||
this.$element.removeData('twbs-pagination');
|
|
||||||
this.$element.off('page');
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
show: function (page) {
|
|
||||||
if (page < 1 || page > this.options.totalPages) {
|
|
||||||
throw new Error('Page is incorrect.');
|
|
||||||
}
|
|
||||||
this.currentPage = page;
|
|
||||||
|
|
||||||
this.$element.trigger('beforePage', page);
|
|
||||||
|
|
||||||
var pages = this.getPages(page);
|
|
||||||
this.render(pages);
|
|
||||||
this.setupEvents();
|
|
||||||
|
|
||||||
this.$element.trigger('page', page);
|
|
||||||
|
|
||||||
return pages;
|
|
||||||
},
|
|
||||||
|
|
||||||
enable: function () {
|
|
||||||
this.show(this.currentPage);
|
|
||||||
},
|
|
||||||
|
|
||||||
disable: function () {
|
|
||||||
var _this = this;
|
|
||||||
this.$listContainer.off('click').on('click', 'li', function (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
});
|
|
||||||
this.$listContainer.children().each(function () {
|
|
||||||
var $this = $(this);
|
|
||||||
if (!$this.hasClass(_this.options.activeClass)) {
|
|
||||||
$(this).addClass(_this.options.disabledClass);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
buildListItems: function (pages) {
|
|
||||||
var listItems = [];
|
|
||||||
|
|
||||||
if (this.options.first) {
|
|
||||||
listItems.push(this.buildItem('first', 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.prev) {
|
|
||||||
var prev = pages.currentPage > 1 ? pages.currentPage - 1 : this.options.loop ? this.options.totalPages : 1;
|
|
||||||
listItems.push(this.buildItem('prev', prev));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < pages.numeric.length; i++) {
|
|
||||||
listItems.push(this.buildItem('page', pages.numeric[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.next) {
|
|
||||||
var next = pages.currentPage < this.options.totalPages ? pages.currentPage + 1 : this.options.loop ? 1 : this.options.totalPages;
|
|
||||||
listItems.push(this.buildItem('next', next));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.last) {
|
|
||||||
listItems.push(this.buildItem('last', this.options.totalPages));
|
|
||||||
}
|
|
||||||
|
|
||||||
return listItems;
|
|
||||||
},
|
|
||||||
|
|
||||||
buildItem: function (type, page) {
|
|
||||||
var $itemContainer = $('<li></li>'),
|
|
||||||
$itemContent = $('<a></a>'),
|
|
||||||
itemText = this.options[type] ? this.makeText(this.options[type], page) : page;
|
|
||||||
|
|
||||||
$itemContainer.addClass(this.options[type + 'Class']);
|
|
||||||
$itemContainer.data('page', page);
|
|
||||||
$itemContainer.data('page-type', type);
|
|
||||||
$itemContainer.append($itemContent.attr('href', this.makeHref(page)).addClass(this.options.anchorClass).html(itemText));
|
|
||||||
|
|
||||||
return $itemContainer;
|
|
||||||
},
|
|
||||||
|
|
||||||
getPages: function (currentPage) {
|
|
||||||
var pages = [];
|
|
||||||
|
|
||||||
var half = Math.floor(this.options.visiblePages / 2);
|
|
||||||
var start = currentPage - half + 1 - this.options.visiblePages % 2;
|
|
||||||
var end = currentPage + half;
|
|
||||||
|
|
||||||
var visiblePages = this.options.visiblePages;
|
|
||||||
if (visiblePages > this.options.totalPages) {
|
|
||||||
visiblePages = this.options.totalPages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle boundary case
|
|
||||||
if (start <= 0) {
|
|
||||||
start = 1;
|
|
||||||
end = visiblePages;
|
|
||||||
}
|
|
||||||
if (end > this.options.totalPages) {
|
|
||||||
start = this.options.totalPages - visiblePages + 1;
|
|
||||||
end = this.options.totalPages;
|
|
||||||
}
|
|
||||||
|
|
||||||
var itPage = start;
|
|
||||||
while (itPage <= end) {
|
|
||||||
pages.push(itPage);
|
|
||||||
itPage++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {"currentPage": currentPage, "numeric": pages};
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function (pages) {
|
|
||||||
var _this = this;
|
|
||||||
this.$listContainer.children().remove();
|
|
||||||
var items = this.buildListItems(pages);
|
|
||||||
$.each(items, function(key, item){
|
|
||||||
_this.$listContainer.append(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$listContainer.children().each(function () {
|
|
||||||
var $this = $(this),
|
|
||||||
pageType = $this.data('page-type');
|
|
||||||
|
|
||||||
switch (pageType) {
|
|
||||||
case 'page':
|
|
||||||
if ($this.data('page') === pages.currentPage) {
|
|
||||||
$this.addClass(_this.options.activeClass);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'first':
|
|
||||||
$this.toggleClass(_this.options.disabledClass, pages.currentPage === 1);
|
|
||||||
break;
|
|
||||||
case 'last':
|
|
||||||
$this.toggleClass(_this.options.disabledClass, pages.currentPage === _this.options.totalPages);
|
|
||||||
break;
|
|
||||||
case 'prev':
|
|
||||||
$this.toggleClass(_this.options.disabledClass, !_this.options.loop && pages.currentPage === 1);
|
|
||||||
break;
|
|
||||||
case 'next':
|
|
||||||
$this.toggleClass(_this.options.disabledClass,
|
|
||||||
!_this.options.loop && pages.currentPage === _this.options.totalPages);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setupEvents: function () {
|
|
||||||
var _this = this;
|
|
||||||
this.$listContainer.off('click').on('click', 'li', function (evt) {
|
|
||||||
var $this = $(this);
|
|
||||||
if ($this.hasClass(_this.options.disabledClass) || $this.hasClass(_this.options.activeClass)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Prevent click event if href is not set.
|
|
||||||
!_this.options.href && evt.preventDefault();
|
|
||||||
_this.show(parseInt($this.data('page')));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
changeTotalPages: function(totalPages, currentPage) {
|
|
||||||
this.options.totalPages = totalPages;
|
|
||||||
return this.show(currentPage);
|
|
||||||
},
|
|
||||||
|
|
||||||
makeHref: function (page) {
|
|
||||||
return this.options.href ? this.generateQueryString(page) : "#";
|
|
||||||
},
|
|
||||||
|
|
||||||
makeText: function (text, page) {
|
|
||||||
return text.replace(this.options.pageVariable, page)
|
|
||||||
.replace(this.options.totalPagesVariable, this.options.totalPages)
|
|
||||||
},
|
|
||||||
|
|
||||||
getPageFromQueryString: function (searchStr) {
|
|
||||||
var search = this.getSearchString(searchStr),
|
|
||||||
regex = new RegExp(this.options.pageVariable + '(=([^&#]*)|&|#|$)'),
|
|
||||||
page = regex.exec(search);
|
|
||||||
if (!page || !page[2]) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
page = decodeURIComponent(page[2]);
|
|
||||||
page = parseInt(page);
|
|
||||||
if (isNaN(page)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return page;
|
|
||||||
},
|
|
||||||
|
|
||||||
generateQueryString: function (pageNumber, searchStr) {
|
|
||||||
var search = this.getSearchString(searchStr),
|
|
||||||
regex = new RegExp(this.options.pageVariable + '=*[^&#]*');
|
|
||||||
if (!search) return '';
|
|
||||||
return '?' + search.replace(regex, this.options.pageVariable + '=' + pageNumber);
|
|
||||||
},
|
|
||||||
|
|
||||||
getSearchString: function (searchStr) {
|
|
||||||
var search = searchStr || window.location.search;
|
|
||||||
if (search === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (search.indexOf('?') === 0) search = search.substr(1);
|
|
||||||
return search;
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrentPage: function () {
|
|
||||||
return this.currentPage;
|
|
||||||
},
|
|
||||||
|
|
||||||
getTotalPages: function () {
|
|
||||||
return this.options.totalPages;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// PLUGIN DEFINITION
|
|
||||||
|
|
||||||
$.fn.twbsPagination = function (option) {
|
|
||||||
var args = Array.prototype.slice.call(arguments, 1);
|
|
||||||
var methodReturn;
|
|
||||||
|
|
||||||
var $this = $(this);
|
|
||||||
var data = $this.data('twbs-pagination');
|
|
||||||
var options = typeof option === 'object' ? option : {};
|
|
||||||
|
|
||||||
if (!data) $this.data('twbs-pagination', (data = new TwbsPagination(this, options) ));
|
|
||||||
if (typeof option === 'string') methodReturn = data[ option ].apply(data, args);
|
|
||||||
|
|
||||||
return ( methodReturn === undefined ) ? $this : methodReturn;
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.twbsPagination.defaults = {
|
|
||||||
totalPages: 1,
|
|
||||||
startPage: 1,
|
|
||||||
visiblePages: 5,
|
|
||||||
initiateStartPageClick: true,
|
|
||||||
hideOnlyOnePage: false,
|
|
||||||
href: false,
|
|
||||||
pageVariable: '{{page}}',
|
|
||||||
totalPagesVariable: '{{total_pages}}',
|
|
||||||
page: null,
|
|
||||||
first: 'First',
|
|
||||||
prev: 'Previous',
|
|
||||||
next: 'Next',
|
|
||||||
last: 'Last',
|
|
||||||
loop: false,
|
|
||||||
beforePageClick: null,
|
|
||||||
onPageClick: null,
|
|
||||||
paginationClass: 'pagination',
|
|
||||||
nextClass: 'page-item next',
|
|
||||||
prevClass: 'page-item prev',
|
|
||||||
lastClass: 'page-item last',
|
|
||||||
firstClass: 'page-item first',
|
|
||||||
pageClass: 'page-item',
|
|
||||||
activeClass: 'active',
|
|
||||||
disabledClass: 'disabled',
|
|
||||||
anchorClass: 'page-link'
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.twbsPagination.Constructor = TwbsPagination;
|
|
||||||
|
|
||||||
$.fn.twbsPagination.noConflict = function () {
|
|
||||||
$.fn.twbsPagination = old;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.twbsPagination.version = "1.4.2";
|
|
||||||
|
|
||||||
})(window.jQuery, window, document);
|
|
||||||
@ -1,797 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global
|
|
||||||
userConfirm:false
|
|
||||||
LabelsInfo:false
|
|
||||||
showMessage:false
|
|
||||||
showOverlay:false
|
|
||||||
isDefaultFormat:false
|
|
||||||
*/
|
|
||||||
|
|
||||||
class TaskView {
|
|
||||||
constructor(task, annotationFormats, exportFormats) {
|
|
||||||
this.init(task);
|
|
||||||
this._annotationFormats = annotationFormats;
|
|
||||||
this._exportFormats = exportFormats;
|
|
||||||
|
|
||||||
this._UI = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_disable() {
|
|
||||||
this._UI.find('*').attr('disabled', true).css('opacity', 0.5);
|
|
||||||
this._UI.find('.dashboardJobList').empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
async _remove() {
|
|
||||||
try {
|
|
||||||
await this._task.delete();
|
|
||||||
this._disable();
|
|
||||||
} catch (exception) {
|
|
||||||
let { message } = exception;
|
|
||||||
if (exception instanceof window.cvat.exceptions.ServerError) {
|
|
||||||
message += ` Code: ${exception.code}`;
|
|
||||||
}
|
|
||||||
showMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_update() {
|
|
||||||
$('#dashboardUpdateModal').remove();
|
|
||||||
|
|
||||||
const dashboardUpdateModal = $($('#dashboardUpdateTemplate').html()).appendTo('body');
|
|
||||||
|
|
||||||
// TODO: Use JSON labels format instead of custom
|
|
||||||
$('#dashboardOldLabels').prop('value', LabelsInfo.serialize(this._task.labels.map(el => el.toJSON())));
|
|
||||||
$('#dashboardCancelUpdate').on('click', () => {
|
|
||||||
dashboardUpdateModal.remove();
|
|
||||||
});
|
|
||||||
$('#dashboardSubmitUpdate').on('click', async () => {
|
|
||||||
let jsonLabels = null;
|
|
||||||
try {
|
|
||||||
jsonLabels = LabelsInfo.deserialize($('#dashboardNewLabels').prop('value'));
|
|
||||||
} catch (exception) {
|
|
||||||
showMessage(exception);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const labels = jsonLabels.map(label => new window.cvat.classes.Label(label));
|
|
||||||
this._task.labels = labels;
|
|
||||||
await this._task.save();
|
|
||||||
showMessage('Task has been successfully updated');
|
|
||||||
} catch (exception) {
|
|
||||||
let { message } = exception;
|
|
||||||
if (exception instanceof window.cvat.exceptions.ServerError) {
|
|
||||||
message += ` Code: ${exception.code}`;
|
|
||||||
}
|
|
||||||
showMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboardUpdateModal.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_upload(uploadAnnotationButton, format) {
|
|
||||||
const button = $(uploadAnnotationButton);
|
|
||||||
$(`<input type="file" accept=".${format.format}">`)
|
|
||||||
.on('change', async (onChangeEvent) => {
|
|
||||||
const file = onChangeEvent.target.files[0];
|
|
||||||
$(onChangeEvent.target).remove();
|
|
||||||
if (file) {
|
|
||||||
button.prop('disabled', true);
|
|
||||||
try {
|
|
||||||
await this._task.annotations.upload(file, format);
|
|
||||||
} catch (error) {
|
|
||||||
showMessage(error.message);
|
|
||||||
} finally {
|
|
||||||
button.prop('disabled', false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).click();
|
|
||||||
}
|
|
||||||
|
|
||||||
async _dump(button, format) {
|
|
||||||
button.disabled = true;
|
|
||||||
try {
|
|
||||||
const url = await this._task.annotations.dump(this._task.name, format);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = `${url}`;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
} catch (error) {
|
|
||||||
showMessage(error.message);
|
|
||||||
} finally {
|
|
||||||
button.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _exportDataset(button, formatName) {
|
|
||||||
button.disabled = true;
|
|
||||||
try {
|
|
||||||
const format = this._exportFormats.find((x) => {
|
|
||||||
return x.name == formatName;
|
|
||||||
});
|
|
||||||
if (!format) {
|
|
||||||
throw `Unknown dataset export format '${formatName}'`;
|
|
||||||
}
|
|
||||||
const url = await this._task.annotations.exportDataset(format.tag);
|
|
||||||
const tempElem = document.createElement('a');
|
|
||||||
tempElem.href = `${url}`;
|
|
||||||
document.body.appendChild(tempElem);
|
|
||||||
tempElem.click();
|
|
||||||
tempElem.remove();
|
|
||||||
} catch (error) {
|
|
||||||
showMessage(error.message);
|
|
||||||
} finally {
|
|
||||||
button.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(task) {
|
|
||||||
this._task = task;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(baseURL) {
|
|
||||||
this._UI = $(`<div tid=${this._task.id} class="dashboardItem"> </div>`).append(
|
|
||||||
$(`<center class="dashboardTitleWrapper">
|
|
||||||
<label class="semiBold h1 selectable"> ${this._task.name} </label>
|
|
||||||
</center>`),
|
|
||||||
).append(
|
|
||||||
$(`<center class="dashboardTitleWrapper">
|
|
||||||
<label class="regular selectable"> ${this._task.status} </label>
|
|
||||||
</center>`),
|
|
||||||
).append(
|
|
||||||
$('<div class="dashboardTaskIntro"> </div>').css({
|
|
||||||
'background-image': `url("/api/v1/tasks/${this._task.id}/frames/0")`,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttonsContainer = $('<div class="dashboardButtonsUI"> </div>').appendTo(this._UI);
|
|
||||||
const downloadButton = $('<select class="regular dashboardButtonUI"'
|
|
||||||
+ 'style="text-align-last: center;"> Dump Annotation </select>');
|
|
||||||
$('<option selected disabled> Dump Annotation </option>').appendTo(downloadButton);
|
|
||||||
|
|
||||||
const uploadButton = $('<select class="regular dashboardButtonUI"'
|
|
||||||
+ 'style="text-align-last: center;"> Upload Annotation </select>');
|
|
||||||
$('<option selected disabled> Upload Annotation </option>').appendTo(uploadButton);
|
|
||||||
|
|
||||||
const dumpers = {};
|
|
||||||
const loaders = {};
|
|
||||||
|
|
||||||
for (const format of this._annotationFormats) {
|
|
||||||
for (const dumper of format.dumpers) {
|
|
||||||
dumpers[dumper.name] = dumper;
|
|
||||||
const item = $(`<option>${dumper.name}</li>`);
|
|
||||||
if (isDefaultFormat(dumper.name, this._task.mode)) {
|
|
||||||
item.addClass('bold');
|
|
||||||
}
|
|
||||||
item.appendTo(downloadButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const loader of format.loaders) {
|
|
||||||
loaders[loader.name] = loader;
|
|
||||||
$(`<option>${loader.name}</li>`).appendTo(uploadButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadButton.on('change', (e) => {
|
|
||||||
this._dump(e.target, dumpers[e.target.value]);
|
|
||||||
downloadButton.prop('value', 'Dump Annotation');
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadButton.on('change', (e) => {
|
|
||||||
this._upload(e.target, loaders[e.target.value]);
|
|
||||||
uploadButton.prop('value', 'Upload Annotation');
|
|
||||||
});
|
|
||||||
|
|
||||||
downloadButton.appendTo(buttonsContainer);
|
|
||||||
uploadButton.appendTo(buttonsContainer);
|
|
||||||
|
|
||||||
const exportButton = $('<select class="regular dashboardButtonUI"'
|
|
||||||
+ 'style="text-align-last: center;"> Export as Dataset </select>');
|
|
||||||
$('<option selected disabled> Export as Dataset </option>').appendTo(exportButton);
|
|
||||||
for (const format of this._exportFormats) {
|
|
||||||
const item = $(`<option>${format.name}</li>`);
|
|
||||||
if (format.is_default) {
|
|
||||||
item.addClass('bold');
|
|
||||||
}
|
|
||||||
item.appendTo(exportButton);
|
|
||||||
}
|
|
||||||
exportButton.on('change', (e) => {
|
|
||||||
this._exportDataset(e.target, e.target.value);
|
|
||||||
exportButton.prop('value', 'Export as Dataset');
|
|
||||||
});
|
|
||||||
exportButton.appendTo(buttonsContainer)
|
|
||||||
|
|
||||||
$('<button class="regular dashboardButtonUI"> Update Task </button>').on('click', () => {
|
|
||||||
this._update();
|
|
||||||
}).appendTo(buttonsContainer);
|
|
||||||
|
|
||||||
$('<button class="regular dashboardButtonUI"> Delete Task </button>').on('click', () => {
|
|
||||||
userConfirm('The task will be removed. Are you sure?', () => this._remove());
|
|
||||||
}).appendTo(buttonsContainer);
|
|
||||||
|
|
||||||
if (this._task.bugTracker) {
|
|
||||||
$('<button class="regular dashboardButtonUI"> Open Bug Tracker </button>').on('click', () => {
|
|
||||||
window.open.call(window, this._task.bugTracker);
|
|
||||||
}).appendTo(buttonsContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
const jobsContainer = $('<table class="dashboardJobList regular">');
|
|
||||||
for (const job of this._task.jobs) {
|
|
||||||
const link = `${baseURL}?id=${job.id}`;
|
|
||||||
jobsContainer.append($(`<tr> <td> <a href="${link}"> ${link} </a> </td> </tr>`));
|
|
||||||
}
|
|
||||||
|
|
||||||
this._UI.append($(`
|
|
||||||
<div class="dashboardJobsUI">
|
|
||||||
<center class="dashboardTitleWrapper">
|
|
||||||
<label class="regular h1"> Jobs </label>
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
`).append(jobsContainer));
|
|
||||||
|
|
||||||
if (this._removed) {
|
|
||||||
this._disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._UI;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DashboardView {
|
|
||||||
constructor(metaData, taskData, annotationFormats, exportFormats) {
|
|
||||||
this._dashboardList = taskData.results;
|
|
||||||
this._maxUploadSize = metaData.max_upload_size;
|
|
||||||
this._maxUploadCount = metaData.max_upload_count;
|
|
||||||
this._baseURL = metaData.base_url;
|
|
||||||
this._sharePath = metaData.share_path;
|
|
||||||
this._params = {};
|
|
||||||
this._annotationFormats = annotationFormats;
|
|
||||||
this._exportFormats = exportFormats;
|
|
||||||
|
|
||||||
this._setupList();
|
|
||||||
this._setupTaskSearch();
|
|
||||||
this._setupCreateDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
_setupList() {
|
|
||||||
const dashboardList = $('#dashboardList');
|
|
||||||
const dashboardPagination = $('#dashboardPagination');
|
|
||||||
const baseURL = this._baseURL;
|
|
||||||
|
|
||||||
const defaults = {
|
|
||||||
totalPages: 1,
|
|
||||||
visiblePages: 7,
|
|
||||||
onPageClick: async (_, page) => {
|
|
||||||
dashboardPagination.css({
|
|
||||||
visibility: 'hidden',
|
|
||||||
});
|
|
||||||
|
|
||||||
const overlay = showOverlay('Loading..');
|
|
||||||
dashboardList.empty();
|
|
||||||
|
|
||||||
let tasks = null;
|
|
||||||
try {
|
|
||||||
const id = (new URLSearchParams(window.location.search)).get('id');
|
|
||||||
const filters = Object.assign({}, {
|
|
||||||
page,
|
|
||||||
}, this._params);
|
|
||||||
|
|
||||||
if (id !== null) {
|
|
||||||
filters.id = +id;
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks = await window.cvat.tasks.get(filters);
|
|
||||||
} catch (exception) {
|
|
||||||
let { message } = exception;
|
|
||||||
if (exception instanceof window.cvat.exceptions.ServerError) {
|
|
||||||
message += ` Code: ${exception.code}`;
|
|
||||||
}
|
|
||||||
showMessage(message);
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
overlay.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
let startPage = dashboardPagination.twbsPagination('getCurrentPage');
|
|
||||||
if (!Number.isInteger(startPage)) {
|
|
||||||
startPage = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboardPagination.twbsPagination('destroy');
|
|
||||||
dashboardPagination.twbsPagination(Object.assign({}, defaults, {
|
|
||||||
totalPages: Math.max(1, Math.ceil(tasks.count / 10)),
|
|
||||||
startPage,
|
|
||||||
initiateStartPageClick: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
for (const task of tasks) {
|
|
||||||
const taskView = new TaskView(task,
|
|
||||||
this._annotationFormats, this._exportFormats);
|
|
||||||
dashboardList.append(taskView.render(baseURL));
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboardPagination.css({
|
|
||||||
'margin-left': (window.screen.width - dashboardPagination.width()) / 2,
|
|
||||||
visibility: 'visible',
|
|
||||||
});
|
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('dashboardReady', {
|
|
||||||
detail: tasks,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
dashboardPagination.twbsPagination(defaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
_setupTaskSearch() {
|
|
||||||
const dashboardPagination = $('#dashboardPagination');
|
|
||||||
const searchInput = $('#dashboardSearchInput');
|
|
||||||
const searchSubmit = $('#dashboardSearchSubmit');
|
|
||||||
|
|
||||||
searchInput.on('keypress', (e) => {
|
|
||||||
if (e.keyCode !== 13) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._params = {};
|
|
||||||
const search = e.target.value.replace(/\s+/g, ' ').replace(/\s*:+\s*/g, ':').trim();
|
|
||||||
for (const field of ['name', 'mode', 'owner', 'assignee', 'status', 'id']) {
|
|
||||||
for (let param of search.split(/[\s]+and[\s]+|[\s]+AND[\s]+/)) {
|
|
||||||
if (param.includes(':')) {
|
|
||||||
param = param.split(':');
|
|
||||||
if (param[0] === field && param[1]) {
|
|
||||||
[, this._params[field]] = param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('id' in this._params) {
|
|
||||||
this._params.id = +this._params.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Object.keys(this._params).length) {
|
|
||||||
this._params.search = search;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.history.replaceState(null, null,
|
|
||||||
`${window.location.origin}${window.location.pathname}`);
|
|
||||||
dashboardPagination.twbsPagination('show', 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
searchSubmit.on('click', () => {
|
|
||||||
const e = $.Event('keypress');
|
|
||||||
e.keyCode = 13;
|
|
||||||
searchInput.trigger(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_setupCreateDialog() {
|
|
||||||
const dashboardCreateTaskButton = $('#dashboardCreateTaskButton');
|
|
||||||
const createModal = $('#dashboardCreateModal');
|
|
||||||
const nameInput = $('#dashboardNameInput');
|
|
||||||
const labelsInput = $('#dashboardLabelsInput');
|
|
||||||
const bugTrackerInput = $('#dashboardBugTrackerInput');
|
|
||||||
const localSourceRadio = $('#dashboardLocalSource');
|
|
||||||
const remoteSourceRadio = $('#dashboardRemoteSource');
|
|
||||||
const shareSourceRadio = $('#dashboardShareSource');
|
|
||||||
const selectFiles = $('#dashboardSelectFiles');
|
|
||||||
const filesLabel = $('#dashboardFilesLabel');
|
|
||||||
const remoteFileInput = $('#dashboardRemoteFileInput');
|
|
||||||
const localFileSelector = $('#dashboardLocalFileSelector');
|
|
||||||
const shareFileSelector = $('#dashboardShareBrowseModal');
|
|
||||||
const shareBrowseTree = $('#dashboardShareBrowser');
|
|
||||||
const cancelBrowseServer = $('#dashboardCancelBrowseServer');
|
|
||||||
const submitBrowseServer = $('#dashboardSubmitBrowseServer');
|
|
||||||
const zOrderBox = $('#dashboardZOrder');
|
|
||||||
const segmentSizeInput = $('#dashboardSegmentSize');
|
|
||||||
const customSegmentSize = $('#dashboardCustomSegment');
|
|
||||||
const overlapSizeInput = $('#dashboardOverlap');
|
|
||||||
const customOverlapSize = $('#dashboardCustomOverlap');
|
|
||||||
const imageQualityInput = $('#dashboardImageQuality');
|
|
||||||
const customCompressQuality = $('#dashboardCustomQuality');
|
|
||||||
const startFrameInput = $('#dashboardStartFrame');
|
|
||||||
const customStartFrame = $('#dashboardCustomStart');
|
|
||||||
const stopFrameInput = $('#dashboardStopFrame');
|
|
||||||
const customStopFrame = $('#dashboardCustomStop');
|
|
||||||
const frameFilterInput = $('#dashboardFrameFilter');
|
|
||||||
const customFrameFilter = $('#dashboardCustomFilter');
|
|
||||||
|
|
||||||
const taskMessage = $('#dashboardCreateTaskMessage');
|
|
||||||
const submitCreate = $('#dashboardSubmitTask');
|
|
||||||
const cancelCreate = $('#dashboardCancelTask');
|
|
||||||
|
|
||||||
let name = nameInput.prop('value');
|
|
||||||
let labels = labelsInput.prop('value');
|
|
||||||
let bugTrackerLink = bugTrackerInput.prop('value').trim();
|
|
||||||
let source = 'local';
|
|
||||||
let zOrder = false;
|
|
||||||
let segmentSize = 5000;
|
|
||||||
let overlapSize = 0;
|
|
||||||
let compressQuality = 50;
|
|
||||||
let startFrame = 0;
|
|
||||||
let stopFrame = 0;
|
|
||||||
let frameFilter = '';
|
|
||||||
let files = [];
|
|
||||||
|
|
||||||
function updateSelectedFiles() {
|
|
||||||
switch (files.length) {
|
|
||||||
case 0:
|
|
||||||
filesLabel.text('No Files');
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
filesLabel.text(typeof (files[0]) === 'string' ? files[0] : files[0].name);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
filesLabel.text(`${files.length} files`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function validateName() {
|
|
||||||
const math = name.match('[a-zA-Z0-9_]+');
|
|
||||||
return math !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateLabels() {
|
|
||||||
try {
|
|
||||||
const result = LabelsInfo.deserialize(labels);
|
|
||||||
return result.length;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateBugTracker() {
|
|
||||||
return !bugTrackerLink || !!bugTrackerLink.match(/^http[s]?/);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateSegmentSize() {
|
|
||||||
return (segmentSize >= 100 && segmentSize <= 50000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateOverlapSize() {
|
|
||||||
return (overlapSize >= 0 && overlapSize <= segmentSize - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateStopFrame(stop, start) {
|
|
||||||
return !customStopFrame.prop('checked') || stop >= start;
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboardCreateTaskButton.on('click', () => {
|
|
||||||
$('#dashboardCreateModal').removeClass('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
nameInput.on('change', (e) => {
|
|
||||||
name = e.target.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
bugTrackerInput.on('change', (e) => {
|
|
||||||
bugTrackerLink = e.target.value.trim();
|
|
||||||
});
|
|
||||||
|
|
||||||
labelsInput.on('change', (e) => {
|
|
||||||
labels = e.target.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
localSourceRadio.on('click', () => {
|
|
||||||
if (source === 'local') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (source === 'remote') {
|
|
||||||
selectFiles.parent().removeClass('hidden');
|
|
||||||
remoteFileInput.parent().addClass('hidden');
|
|
||||||
}
|
|
||||||
source = 'local';
|
|
||||||
files = [];
|
|
||||||
updateSelectedFiles();
|
|
||||||
});
|
|
||||||
|
|
||||||
remoteSourceRadio.on('click', () => {
|
|
||||||
if (source === 'remote') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
source = 'remote';
|
|
||||||
selectFiles.parent().addClass('hidden');
|
|
||||||
remoteFileInput.parent().removeClass('hidden');
|
|
||||||
remoteFileInput.prop('value', '');
|
|
||||||
files = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
shareSourceRadio.on('click', () => {
|
|
||||||
if (source === 'share') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (source === 'remote') {
|
|
||||||
selectFiles.parent().removeClass('hidden');
|
|
||||||
remoteFileInput.parent().addClass('hidden');
|
|
||||||
}
|
|
||||||
source = 'share';
|
|
||||||
files = [];
|
|
||||||
updateSelectedFiles();
|
|
||||||
});
|
|
||||||
|
|
||||||
selectFiles.on('click', () => {
|
|
||||||
if (source === 'local') {
|
|
||||||
localFileSelector.click();
|
|
||||||
} else {
|
|
||||||
shareBrowseTree.jstree('refresh');
|
|
||||||
shareFileSelector.removeClass('hidden');
|
|
||||||
shareBrowseTree.jstree({
|
|
||||||
core: {
|
|
||||||
async data(obj, callback) {
|
|
||||||
const directory = obj.id === '#' ? '' : `${obj.id}/`;
|
|
||||||
|
|
||||||
let shareFiles = await window.cvat.server.share(directory);
|
|
||||||
shareFiles = Array.from(shareFiles, (element) => {
|
|
||||||
const shareFileInfo = {
|
|
||||||
id: `${directory}${element.name}`,
|
|
||||||
children: element.type === 'DIR',
|
|
||||||
text: element.name,
|
|
||||||
icon: element.type === 'DIR' ? 'jstree-folder' : 'jstree-file',
|
|
||||||
};
|
|
||||||
|
|
||||||
return shareFileInfo;
|
|
||||||
});
|
|
||||||
|
|
||||||
callback.call(this, shareFiles);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: ['checkbox', 'sort'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
localFileSelector.on('change', (e) => {
|
|
||||||
const localFiles = e.target.files;
|
|
||||||
files = localFiles;
|
|
||||||
updateSelectedFiles();
|
|
||||||
});
|
|
||||||
|
|
||||||
remoteFileInput.on('change', () => {
|
|
||||||
const text = remoteFileInput.prop('value');
|
|
||||||
files = text.split('\n').map(f => f.trim()).filter(f => f.length > 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
cancelBrowseServer.on('click', () => shareFileSelector.addClass('hidden'));
|
|
||||||
submitBrowseServer.on('click', () => {
|
|
||||||
if (!createModal.hasClass('hidden')) {
|
|
||||||
files = Array.from(shareBrowseTree.jstree(true).get_selected());
|
|
||||||
cancelBrowseServer.click();
|
|
||||||
updateSelectedFiles();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
zOrderBox.on('click', (e) => {
|
|
||||||
zOrder = e.target.checked;
|
|
||||||
});
|
|
||||||
|
|
||||||
customSegmentSize.on('change', e => segmentSizeInput.prop('disabled', !e.target.checked));
|
|
||||||
customOverlapSize.on('change', e => overlapSizeInput.prop('disabled', !e.target.checked));
|
|
||||||
customCompressQuality.on('change', e => imageQualityInput.prop('disabled', !e.target.checked));
|
|
||||||
customStartFrame.on('change', e => startFrameInput.prop('disabled', !e.target.checked));
|
|
||||||
customStopFrame.on('change', e => stopFrameInput.prop('disabled', !e.target.checked));
|
|
||||||
customFrameFilter.on('change', e => frameFilterInput.prop('disabled', !e.target.checked));
|
|
||||||
|
|
||||||
segmentSizeInput.on('change', () => {
|
|
||||||
const value = Math.clamp(
|
|
||||||
+segmentSizeInput.prop('value'),
|
|
||||||
+segmentSizeInput.prop('min'),
|
|
||||||
+segmentSizeInput.prop('max'),
|
|
||||||
);
|
|
||||||
|
|
||||||
segmentSizeInput.prop('value', value);
|
|
||||||
segmentSize = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
overlapSizeInput.on('change', () => {
|
|
||||||
const value = Math.clamp(
|
|
||||||
+overlapSizeInput.prop('value'),
|
|
||||||
+overlapSizeInput.prop('min'),
|
|
||||||
+overlapSizeInput.prop('max'),
|
|
||||||
);
|
|
||||||
|
|
||||||
overlapSizeInput.prop('value', value);
|
|
||||||
overlapSize = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
imageQualityInput.on('change', () => {
|
|
||||||
const value = Math.clamp(
|
|
||||||
+imageQualityInput.prop('value'),
|
|
||||||
+imageQualityInput.prop('min'),
|
|
||||||
+imageQualityInput.prop('max'),
|
|
||||||
);
|
|
||||||
|
|
||||||
imageQualityInput.prop('value', value);
|
|
||||||
compressQuality = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
startFrameInput.on('change', () => {
|
|
||||||
const value = Math.max(
|
|
||||||
+startFrameInput.prop('value'),
|
|
||||||
+startFrameInput.prop('min')
|
|
||||||
);
|
|
||||||
|
|
||||||
startFrameInput.prop('value', value);
|
|
||||||
startFrame = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
stopFrameInput.on('change', () => {
|
|
||||||
const value = Math.max(
|
|
||||||
+stopFrameInput.prop('value'),
|
|
||||||
+stopFrameInput.prop('min')
|
|
||||||
);
|
|
||||||
|
|
||||||
stopFrameInput.prop('value', value);
|
|
||||||
stopFrame = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
frameFilterInput.on('change', () => {
|
|
||||||
frameFilter = frameFilterInput.prop('value');
|
|
||||||
});
|
|
||||||
|
|
||||||
submitCreate.on('click', async () => {
|
|
||||||
if (!validateName(name)) {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('Bad task name');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateLabels()) {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('Bad labels specification');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateSegmentSize()) {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('Segment size out of range');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateBugTracker()) {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('Bad bug tracker link');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateOverlapSize()) {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('Overlap size must be positive and not more then segment size');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateStopFrame(stopFrame, startFrame)) {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('Stop frame must be greater than or equal to start frame');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files.length <= 0) {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('No files specified for the task');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files.length > window.maxUploadCount && source === 'local') {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('Too many files were specified. Please use share to upload');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source === 'local') {
|
|
||||||
let commonSize = 0;
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
commonSize += file.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commonSize > window.maxUploadSize) {
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text('Too big files size. Please use share to upload');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const description = {
|
|
||||||
name,
|
|
||||||
labels: LabelsInfo.deserialize(labels),
|
|
||||||
image_quality: compressQuality,
|
|
||||||
z_order: zOrder,
|
|
||||||
bug_tracker: bugTrackerLink,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (customSegmentSize.prop('checked')) {
|
|
||||||
description.segment_size = segmentSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customOverlapSize.prop('checked')) {
|
|
||||||
description.overlap = overlapSize;
|
|
||||||
}
|
|
||||||
if (customStartFrame.prop('checked')) {
|
|
||||||
description.start_frame = startFrame;
|
|
||||||
}
|
|
||||||
if (customStopFrame.prop('checked')) {
|
|
||||||
description.stop_frame = stopFrame;
|
|
||||||
}
|
|
||||||
if (customFrameFilter.prop('checked')) {
|
|
||||||
description.frame_filter = frameFilter;
|
|
||||||
}
|
|
||||||
if (customStartFrame.prop('checked')) {
|
|
||||||
description.start_frame = startFrame;
|
|
||||||
}
|
|
||||||
if (customStopFrame.prop('checked')) {
|
|
||||||
description.stop_frame = stopFrame;
|
|
||||||
}
|
|
||||||
if (customFrameFilter.prop('checked')) {
|
|
||||||
description.frame_filter = frameFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let task = new window.cvat.classes.Task(description);
|
|
||||||
if (source === 'local') {
|
|
||||||
task.clientFiles = Array.from(files);
|
|
||||||
} else if (source === 'share') {
|
|
||||||
task.serverFiles = Array.from(files);
|
|
||||||
} else if (source === 'remote') {
|
|
||||||
task.remoteFiles = Array.from(files);
|
|
||||||
}
|
|
||||||
submitCreate.attr('disabled', true);
|
|
||||||
cancelCreate.attr('disabled', true);
|
|
||||||
task = await task.save((message) => {
|
|
||||||
taskMessage.css('color', 'green');
|
|
||||||
taskMessage.text(message);
|
|
||||||
});
|
|
||||||
window.location.reload();
|
|
||||||
} catch (exception) {
|
|
||||||
let { message } = exception;
|
|
||||||
if (exception instanceof window.cvat.exceptions.ServerError) {
|
|
||||||
message += ` Code: ${exception.code}`;
|
|
||||||
}
|
|
||||||
taskMessage.css('color', 'red');
|
|
||||||
taskMessage.text(message);
|
|
||||||
submitCreate.attr('disabled', false);
|
|
||||||
cancelCreate.attr('disabled', false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cancelCreate.on('click', () => createModal.addClass('hidden'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DASHBOARD ENTRYPOINT
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
window.cvat.config.backendAPI = `${window.location.origin}/api/v1`;
|
|
||||||
$.when(
|
|
||||||
// TODO: Use REST API in order to get meta
|
|
||||||
$.get('/dashboard/meta'),
|
|
||||||
$.get(`/api/v1/tasks${window.location.search}`),
|
|
||||||
window.cvat.server.formats(),
|
|
||||||
window.cvat.server.datasetFormats(),
|
|
||||||
).then((metaData, taskData, annotationFormats, exportFormats) => {
|
|
||||||
try {
|
|
||||||
new DashboardView(metaData[0], taskData[0],
|
|
||||||
annotationFormats, exportFormats);
|
|
||||||
} catch (exception) {
|
|
||||||
$('#content').empty();
|
|
||||||
const message = `Can not build CVAT dashboard. Exception: ${exception}.`;
|
|
||||||
showMessage(message);
|
|
||||||
}
|
|
||||||
}).fail((errorData) => {
|
|
||||||
$('#content').empty();
|
|
||||||
const message = `Can not build CVAT dashboard. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
}).always(() => {
|
|
||||||
$('#loadingOverlay').remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
$('<button class="menuButton semiBold h2"> Open Task </button>').on('click', () => {
|
|
||||||
const win = window.open(`${window.location.origin}/dashboard/?id=${window.cvat.job.task_id}`, '_blank');
|
|
||||||
win.focus();
|
|
||||||
}).prependTo('#engineMenuButtons');
|
|
||||||
});
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
.dashboardItem {
|
|
||||||
margin: 5px auto;
|
|
||||||
width: 1200px;
|
|
||||||
height: 335px;
|
|
||||||
background-color: #e7edf5;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboardTaskIntro {
|
|
||||||
width: 30%;
|
|
||||||
height: 75%;
|
|
||||||
float: left;
|
|
||||||
margin-left: 20px;
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboardButtonsUI {
|
|
||||||
margin-top: 1%;
|
|
||||||
width: 35%;
|
|
||||||
height: 75%;
|
|
||||||
float: left;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboardButtonUI {
|
|
||||||
display: block;
|
|
||||||
width: 60%;
|
|
||||||
height: 1.6em;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 0.3em;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboardJobsUI {
|
|
||||||
width: 30%;
|
|
||||||
height: 75%;
|
|
||||||
float: left;
|
|
||||||
text-align: center;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboardJobList {
|
|
||||||
-webkit-user-select: text;
|
|
||||||
-moz-user-select: text;
|
|
||||||
-ms-user-select: text;
|
|
||||||
user-select: text;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboardTitleWrapper {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dashboardSearchInput {
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid grey;
|
|
||||||
float: left;
|
|
||||||
width: 76%;
|
|
||||||
background: #f1f1f1;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dashboardSearchSubmit {
|
|
||||||
float: left;
|
|
||||||
width: 20%;
|
|
||||||
padding: 10px;
|
|
||||||
background: #0b7dda;
|
|
||||||
border: 1px solid grey;
|
|
||||||
border-left: none;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dashboardSearchSubmit:hover {
|
|
||||||
background: #4c92ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dashboardSearchSubmit:active {
|
|
||||||
background: #0b7fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dashboardCreateContent {
|
|
||||||
width: 500px;
|
|
||||||
display: table;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dashboardShareBrowser {
|
|
||||||
width: 100%;
|
|
||||||
height: 80%;
|
|
||||||
border: 1px solid black;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dashboardUpdateContent {
|
|
||||||
width: 450px;
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#dashboardOldLabels {
|
|
||||||
width: 97%;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#dashboardNewLabels {
|
|
||||||
width: 97%;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
@ -1,223 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (C) 2018 Intel Corporation
|
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
|
||||||
-->
|
|
||||||
|
|
||||||
{% extends 'engine/base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
{% load pagination_tags %}
|
|
||||||
|
|
||||||
{% block head_title %}
|
|
||||||
CVAT Dashboard
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block head_css %}
|
|
||||||
{{ block.super }}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/stylesheet.css' %}">
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/jstree/themes/default/style.min.css' %}">
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/pagination/bootstrap.css' %}">
|
|
||||||
{% for css_file in css_3rdparty %}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static css_file %}">
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block head_js_3rdparty %}
|
|
||||||
{{ block.super }}
|
|
||||||
<script type="text/javascript" src="{% static 'dashboard/js/3rdparty/jstree/jstree.min.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'dashboard/js/3rdparty/pagination/jquery.twbsPagination.js' %}"></script>
|
|
||||||
{% for js_file in js_3rdparty %}
|
|
||||||
<script type="text/javascript" src="{% static js_file %}" defer></script>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block head_js_cvat %}
|
|
||||||
{{ block.super }}
|
|
||||||
<script type="text/javascript" src="{% static 'engine/js/cvat-core.min.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'dashboard/js/dashboard.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'engine/js/listener.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'engine/js/labelsInfo.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'engine/js/shapes.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'engine/js/annotationParser.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'engine/js/server.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div id="content">
|
|
||||||
<div style="width: 100%; display: flex;">
|
|
||||||
<div style="width: 50%; display: flex;"> </div>
|
|
||||||
<div style="width: 100%; display: flex;">
|
|
||||||
<div id="dashboardManageButtons" style="display: flex;">
|
|
||||||
<button id="dashboardCreateTaskButton" class="regular h1" style="padding: 7px;"> Create New Task </button>
|
|
||||||
</div>
|
|
||||||
<div style="width: 300px; display: flex; margin-left: 4px;">
|
|
||||||
<input type="text" id="dashboardSearchInput" class="regular h3" placeholder="Search.." name="search">
|
|
||||||
<button id="dashboardSearchSubmit" class="regular h3"> 🔍 </button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="width: 50%; display: flex;"> </div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="dashboardList" style="float: left; width: 100%"> </div>
|
|
||||||
<ul id="dashboardPagination" class="pagination-sm"></ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="dashboardCreateModal" class="modal hidden">
|
|
||||||
<form id="dashboardCreateContent" class="modal-content" autocomplete="on" onsubmit="return false">
|
|
||||||
<center>
|
|
||||||
<label class="semiBold h1"> Task Configuration </label>
|
|
||||||
</center>
|
|
||||||
|
|
||||||
<table style="width: 100%; text-align: left;">
|
|
||||||
<tr>
|
|
||||||
<td style="width: 25%"> <label class="regular h2"> Name: </label> </td>
|
|
||||||
<td> <input type="text" id="dashboardNameInput" class="regular" style="width: 90%"/> </td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td> <label class="regular h2"> Labels: </label> </td>
|
|
||||||
<td> <input type="text" id="dashboardLabelsInput" class="regular" style="width: 90%" title='
|
|
||||||
Example:
|
|
||||||
car @select=color:blue,red,black ~checkbox=parked:true
|
|
||||||
@text=plate:"" @select=model:volvo,mazda,bmw
|
|
||||||
~radio=quality:good,bad
|
|
||||||
|
|
||||||
Specification:
|
|
||||||
<prefix>checkbox=id:true/false
|
|
||||||
<prefix>radio=id:name1,name2,...
|
|
||||||
<prefix>number=id:min,max,step
|
|
||||||
<prefix>text=id:"default value"
|
|
||||||
<prefix>select=id:value1,value2,...
|
|
||||||
|
|
||||||
<prefix> can be @ for unique properties and ~ for properties
|
|
||||||
which can change its value during lifetime of the object.
|
|
||||||
Default value for all elements is the first value after ":".
|
|
||||||
|
|
||||||
For "select" and "radio" types the special value is available: "__undefined__".
|
|
||||||
Specify the value FIRST if the attribute should be annotated explicitly.
|
|
||||||
Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td> <label class="regular h2"> Bug Tracker: </label> </td>
|
|
||||||
<td> <input type="text" id="dashboardBugTrackerInput" class="regular" style="width: 90%", placeholder="Please specify full URL"/> </td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td> <label class="regular h2"> Source: </label> </td>
|
|
||||||
<td>
|
|
||||||
<input id="dashboardLocalSource" type="radio" name="sourceType" value="local" checked=true/> <label for="dashboardLocalSource" class="regular h2" for="localSource"> Local </label>
|
|
||||||
<br> <input id="dashboardRemoteSource" type="radio" name="sourceType" value="remote"/> <label for="dashboardRemoteSource" class="regular h2"> Remote </label>
|
|
||||||
<br> <input id="dashboardShareSource" type="radio" name="sourceType" value="share"/> <label for="dashboardShareSource" class="regular h2" for="shareSource"> Share </label>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="regular h2"> Z-Order </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" id="dashboardZOrder"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="regular h2"> Overlap Size </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="number" id="dashboardOverlap" class="regular" max="50000" min="0" value="0" disabled=true/>
|
|
||||||
<input type="checkbox" id="dashboardCustomOverlap" title="Custom overlap size"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="regular h2"> Segment Size </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="number" id="dashboardSegmentSize" class="regular" max="50000" min="100" value="5000" disabled=true/>
|
|
||||||
<input type="checkbox" id="dashboardCustomSegment" title="Custom segment size"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="regular h2"> Image Quality </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="number" id="dashboardImageQuality" class="regular" style="width: 4.5em;" max="95" min="1" value="50" disabled=true/>
|
|
||||||
<input type="checkbox" id="dashboardCustomQuality" title="Custom image quality"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="regular h2"> Start Frame </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="number" id="dashboardStartFrame" class="regular" style="width: 4.5em;" min="0" value=0 disabled=true/>
|
|
||||||
<input type="checkbox" id="dashboardCustomStart" title="Custom start frame"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="regular h2"> Stop Frame </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="number" id="dashboardStopFrame" class="regular" style="width: 4.5em;" min="0" value=0 disabled=true/>
|
|
||||||
<input type="checkbox" id="dashboardCustomStop" title="Custom stop frame"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="regular h2"> Frame Filter </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="text" id="dashboardFrameFilter" class="regular" style="width: 4.5em;" title="Currently only support 'step=K' filter expression." disabled=true/>
|
|
||||||
<input type="checkbox" id="dashboardCustomFilter" title="Custom frame filter"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
<div style="text-align: left;">
|
|
||||||
<div>
|
|
||||||
<button id="dashboardSelectFiles" class="regular h2"> Select Files </button>
|
|
||||||
<label id="dashboardFilesLabel" class="regular h2" style="margin-left: 10px"> No Files </label>
|
|
||||||
<input id="dashboardLocalFileSelector" type="file" style="display: none" multiple/>
|
|
||||||
</div>
|
|
||||||
<div class="hidden">
|
|
||||||
<label for="dashboardRemoteFileInput" class="regular h2"> URLs: </label><br>
|
|
||||||
<textarea id="dashboardRemoteFileInput" rows="5" placeholder="One URL per line" style="width: 100%;"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="width: 100%; height: 14%; padding-top: 10px;">
|
|
||||||
<div style="float: right; width: 35%; height: 50px;">
|
|
||||||
<button id="dashboardCancelTask" class="regular h2"> Cancel </button>
|
|
||||||
<button id="dashboardSubmitTask" class="regular h2"> Submit </button>
|
|
||||||
</div>
|
|
||||||
<div style="float: left; height: 50px; overflow: auto; width: 100%; height: auto; word-break: break-word;">
|
|
||||||
<label id="dashboardCreateTaskMessage" class="regular h2 selectable" style="float:left;"> </label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="dashboardShareBrowseModal" class="modal hidden">
|
|
||||||
<div style="width: 600px; height: 400px;" class="modal-content noSelect">
|
|
||||||
<center> <label id="dashboardShareBasePath" class="regular h1"> </label> </center>
|
|
||||||
<div id="dashboardShareBrowser"> </div>
|
|
||||||
<center>
|
|
||||||
<button id="dashboardCancelBrowseServer" class="regular h2" style="margin: 0px 10px"> Cancel </button>
|
|
||||||
<button id="dashboardSubmitBrowseServer" class="regular h2" style="margin: 0px 10px"> Submit </button>
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template id="dashboardUpdateTemplate">
|
|
||||||
<div id="dashboardUpdateModal" class="modal">
|
|
||||||
<div id="dashboardUpdateContent" class="modal-content">
|
|
||||||
<input id="dashboardOldLabels" type="text" readonly=true class="regular h2">
|
|
||||||
<input id="dashboardNewLabels" type="text" placeholder="expand the specification here" class="regular h2">
|
|
||||||
<center>
|
|
||||||
<button id="dashboardCancelUpdate" class="regular h2"> Cancel </button>
|
|
||||||
<button id="dashboardSubmitUpdate" class="regular h2"> Update </button>
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
# Copyright (C) 2018 Intel Corporation
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
from django.urls import path
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('', views.DashboardView),
|
|
||||||
path('meta', views.DashboardMeta),
|
|
||||||
]
|
|
||||||
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
|
|
||||||
# Copyright (C) 2018 Intel Corporation
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.conf import settings
|
|
||||||
from cvat.apps.authentication.decorators import login_required
|
|
||||||
|
|
||||||
from cvat.settings.base import JS_3RDPARTY, CSS_3RDPARTY
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def DashboardView(request):
|
|
||||||
return render(request, 'dashboard/dashboard.html', {
|
|
||||||
'js_3rdparty': JS_3RDPARTY.get('dashboard', []),
|
|
||||||
'css_3rdparty': CSS_3RDPARTY.get('dashboard', []),
|
|
||||||
})
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def DashboardMeta(request):
|
|
||||||
return JsonResponse({
|
|
||||||
'max_upload_size': settings.LOCAL_LOAD_MAX_FILES_SIZE,
|
|
||||||
'max_upload_count': settings.LOCAL_LOAD_MAX_FILES_COUNT,
|
|
||||||
'base_url': "{0}://{1}/".format(request.scheme, request.get_host()),
|
|
||||||
'share_path': os.getenv('CVAT_SHARE_URL', default=r'${cvat_root}/share'),
|
|
||||||
})
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
$('<button class="regular h1" style="margin-left: 5px;"> User Guide </button>').on('click', () => {
|
|
||||||
window.open('/documentation/user_guide.html');
|
|
||||||
}).appendTo('#dashboardManageButtons');
|
|
||||||
});
|
|
||||||
@ -1,290 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global
|
|
||||||
showMessage:false
|
|
||||||
*/
|
|
||||||
|
|
||||||
// GIT ENTRYPOINT
|
|
||||||
window.addEventListener('dashboardReady', () => {
|
|
||||||
const reposWindowId = 'gitReposWindow';
|
|
||||||
const closeReposWindowButtonId = 'closeGitReposButton';
|
|
||||||
const reposURLTextId = 'gitReposURLText';
|
|
||||||
const reposSyncButtonId = 'gitReposSyncButton';
|
|
||||||
const labelStatusId = 'gitReposLabelStatus';
|
|
||||||
const labelMessageId = 'gitReposLabelMessage';
|
|
||||||
|
|
||||||
const reposWindowTemplate = `
|
|
||||||
<div id="${reposWindowId}" class="modal">
|
|
||||||
<div style="width: 700px; height: auto;" class="modal-content">
|
|
||||||
<div style="width: 100%; height: 60%; overflow-y: auto;">
|
|
||||||
<table style="width: 100%;">
|
|
||||||
<tr>
|
|
||||||
<td style="width: 20%;">
|
|
||||||
<label class="regular h2"> Repository URL: </label>
|
|
||||||
</td>
|
|
||||||
<td style="width: 80%;" colspan="2">
|
|
||||||
<input class="regular h2" type="text" style="width: 92%;" id="${reposURLTextId}" readonly/>
|
|
||||||
</td>
|
|
||||||
</td>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 20%;">
|
|
||||||
<label class="regular h2"> Status: </label>
|
|
||||||
</td>
|
|
||||||
<td style="width: 60%;">
|
|
||||||
<div>
|
|
||||||
<label class="regular h2" id="${labelStatusId}"> </label>
|
|
||||||
<label class="regular h2" id="${labelMessageId}" style="word-break: break-word; user-select: text;"> </label>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td style="width: 20%;">
|
|
||||||
<button style="width: 70%;" id="${reposSyncButtonId}" class="regular h2"> Sync </button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<center>
|
|
||||||
<button id="${closeReposWindowButtonId}" class="regular h1" style="margin-top: 15px;"> Close </button>
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
$.get('/git/repository/meta/get').done((gitData) => {
|
|
||||||
const dashboardItems = $('.dashboardItem');
|
|
||||||
dashboardItems.each(function setupDashboardItem() {
|
|
||||||
const tid = +this.getAttribute('tid');
|
|
||||||
if (tid in gitData) {
|
|
||||||
if (['sync', 'syncing'].includes(gitData[tid])) {
|
|
||||||
this.style.background = 'floralwhite';
|
|
||||||
} else if (gitData[tid] === 'merged') {
|
|
||||||
this.style.background = 'azure';
|
|
||||||
} else {
|
|
||||||
this.style.background = 'mistyrose';
|
|
||||||
}
|
|
||||||
|
|
||||||
$('<button> Git Repository Sync </button>').addClass('regular dashboardButtonUI').on('click', () => {
|
|
||||||
$(`#${reposWindowId}`).remove();
|
|
||||||
const gitWindow = $(reposWindowTemplate).appendTo('body');
|
|
||||||
const closeReposWindowButton = $(`#${closeReposWindowButtonId}`);
|
|
||||||
const reposSyncButton = $(`#${reposSyncButtonId}`);
|
|
||||||
const gitLabelMessage = $(`#${labelMessageId}`);
|
|
||||||
const gitLabelStatus = $(`#${labelStatusId}`);
|
|
||||||
const reposURLText = $(`#${reposURLTextId}`);
|
|
||||||
|
|
||||||
function updateState() {
|
|
||||||
reposURLText.attr('placeholder', 'Waiting for server response..');
|
|
||||||
reposURLText.prop('value', '');
|
|
||||||
gitLabelMessage.css('color', '#cccc00').text('Waiting for server response..');
|
|
||||||
gitLabelStatus.css('color', '#cccc00').text('\u25cc');
|
|
||||||
reposSyncButton.attr('disabled', true);
|
|
||||||
|
|
||||||
$.get(`/git/repository/get/${tid}`).done((data) => {
|
|
||||||
reposURLText.attr('placeholder', '');
|
|
||||||
reposURLText.prop('value', data.url.value);
|
|
||||||
|
|
||||||
if (!data.status.value) {
|
|
||||||
gitLabelStatus.css('color', 'red').text('\u26a0');
|
|
||||||
gitLabelMessage.css('color', 'red').text(data.status.error);
|
|
||||||
reposSyncButton.attr('disabled', false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.status.value === '!sync') {
|
|
||||||
gitLabelStatus.css('color', 'red').text('\u2606');
|
|
||||||
gitLabelMessage.css('color', 'red').text('Repository is not synchronized');
|
|
||||||
reposSyncButton.attr('disabled', false);
|
|
||||||
} else if (data.status.value === 'sync') {
|
|
||||||
gitLabelStatus.css('color', '#cccc00').text('\u2605');
|
|
||||||
gitLabelMessage.css('color', 'black').text('Synchronized (merge required)');
|
|
||||||
} else if (data.status.value === 'merged') {
|
|
||||||
gitLabelStatus.css('color', 'darkgreen').text('\u2605');
|
|
||||||
gitLabelMessage.css('color', 'darkgreen').text('Synchronized');
|
|
||||||
} else if (data.status.value === 'syncing') {
|
|
||||||
gitLabelMessage.css('color', '#cccc00').text('Synchronization..');
|
|
||||||
gitLabelStatus.css('color', '#cccc00').text('\u25cc');
|
|
||||||
} else {
|
|
||||||
const message = `Got unknown repository status: ${data.status.value}`;
|
|
||||||
gitLabelStatus.css('color', 'red').text('\u26a0');
|
|
||||||
gitLabelMessage.css('color', 'red').text(message);
|
|
||||||
}
|
|
||||||
}).fail((data) => {
|
|
||||||
gitWindow.remove();
|
|
||||||
const message = 'Error occured during get an repos status. '
|
|
||||||
+ `Code: ${data.status}, text: ${data.responseText || data.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
closeReposWindowButton.on('click', () => {
|
|
||||||
gitWindow.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
reposSyncButton.on('click', () => {
|
|
||||||
function badResponse(message) {
|
|
||||||
try {
|
|
||||||
showMessage(message);
|
|
||||||
throw Error(message);
|
|
||||||
} finally {
|
|
||||||
gitWindow.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gitLabelMessage.css('color', '#cccc00').text('Synchronization..');
|
|
||||||
gitLabelStatus.css('color', '#cccc00').text('\u25cc');
|
|
||||||
reposSyncButton.attr('disabled', true);
|
|
||||||
|
|
||||||
$.get(`/git/repository/push/${tid}`).done((rqData) => {
|
|
||||||
function checkCallback() {
|
|
||||||
$.get(`/git/repository/check/${rqData.rq_id}`).done((statusData) => {
|
|
||||||
if (['queued', 'started'].includes(statusData.status)) {
|
|
||||||
setTimeout(checkCallback, 1000);
|
|
||||||
} else if (statusData.status === 'finished') {
|
|
||||||
updateState();
|
|
||||||
} else if (statusData.status === 'failed') {
|
|
||||||
const message = `Can not push to remote repository. Message: ${statusData.stderr}`;
|
|
||||||
badResponse(message);
|
|
||||||
} else {
|
|
||||||
const message = `Check returned status "${statusData.status}".`;
|
|
||||||
badResponse(message);
|
|
||||||
}
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = 'Errors occured during pushing an repos entry. '
|
|
||||||
+ `Code: ${errorData.status}, text: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
badResponse(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(checkCallback, 1000);
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = 'Errors occured during pushing an repos entry. '
|
|
||||||
+ `Code: ${errorData.status}, text: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
badResponse(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
updateState();
|
|
||||||
}).appendTo($(this).find('div.dashboardButtonsUI')[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not get repository meta information. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const createURLInputTextId = 'gitCreateURLInputText';
|
|
||||||
const lfsCheckboxId = 'gitLFSCheckbox';
|
|
||||||
|
|
||||||
// Setup the "Create task" dialog
|
|
||||||
const title = 'Field for a repository URL and a relative path inside the repository. \n'
|
|
||||||
+ 'Default repository path is `annotation/<dump_file_name>.zip`. \n'
|
|
||||||
+ 'There are .zip or .xml extenstions are supported.';
|
|
||||||
const placeh = 'github.com/user/repos [annotation/<dump_file_name>.zip]';
|
|
||||||
|
|
||||||
$(`
|
|
||||||
<tr>
|
|
||||||
<td> <label class="regular h2"> Dataset Repository: </label> </td>
|
|
||||||
<td>
|
|
||||||
<input type="text" id="${createURLInputTextId}" class="regular" style="width: 90%", placeholder="${placeh}" title="${title}"/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td> <label class="regular h2" checked> Use LFS: </label> </td>
|
|
||||||
<td> <input type="checkbox" checked id="${lfsCheckboxId}" </td>
|
|
||||||
</tr>`).insertAfter($('#dashboardBugTrackerInput').parent().parent());
|
|
||||||
|
|
||||||
async function cloneRepos(_, createdTask) {
|
|
||||||
async function wait(rqId) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
async function checkCallback() {
|
|
||||||
let response = null;
|
|
||||||
try {
|
|
||||||
response = await $.get(`/git/repository/check/${rqId}`);
|
|
||||||
} catch (errorData) {
|
|
||||||
const message = `Can not sent a request to clone the repository. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
reject(new Error(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
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(new Error(message));
|
|
||||||
} else {
|
|
||||||
const message = `Repository status check returned the status "${response.status}"`;
|
|
||||||
reject(new Error(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(checkCallback, 1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskMessage = $('#dashboardCreateTaskMessage');
|
|
||||||
|
|
||||||
const path = $(`#${createURLInputTextId}`).prop('value').replace(/\s/g, '');
|
|
||||||
const lfs = $(`#${lfsCheckboxId}`).prop('checked');
|
|
||||||
|
|
||||||
let response = null;
|
|
||||||
if (path.length) {
|
|
||||||
taskMessage.text('Git repository is being cloned..');
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await $.ajax({
|
|
||||||
url: `/git/repository/create/${createdTask.id}`,
|
|
||||||
type: 'POST',
|
|
||||||
data: JSON.stringify({
|
|
||||||
path,
|
|
||||||
lfs,
|
|
||||||
tid: createdTask.id,
|
|
||||||
}),
|
|
||||||
contentType: 'application/json',
|
|
||||||
});
|
|
||||||
} catch (errorData) {
|
|
||||||
createdTask.delete();
|
|
||||||
const message = `Can not send a request to clone the repository. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await wait(response.rq_id);
|
|
||||||
} catch (exception) {
|
|
||||||
createdTask.delete();
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
taskMessage.text('Git repository has been cloned..');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gitPlugin = {
|
|
||||||
name: 'Git Plugin',
|
|
||||||
description: 'Plugin allows you to attach a repository to a task',
|
|
||||||
cvat: {
|
|
||||||
classes: {
|
|
||||||
Task: {
|
|
||||||
prototype: {
|
|
||||||
save: {
|
|
||||||
leave: cloneRepos,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
window.cvat.plugins.register(gitPlugin);
|
|
||||||
});
|
|
||||||
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
$('<button class="regular h1" style="margin-left: 5px;"> Analytics </button>').on('click', () => {
|
|
||||||
window.open('/analytics/app/kibana');
|
|
||||||
}).appendTo('#dashboardManageButtons');
|
|
||||||
});
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 Intel Corporation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global
|
|
||||||
userConfirm:false
|
|
||||||
showMessage:false
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.addEventListener('dashboardReady', () => {
|
|
||||||
function checkProcess(tid, button) {
|
|
||||||
function checkCallback() {
|
|
||||||
$.get(`/tensorflow/annotation/check/task/${tid}`).done((statusData) => {
|
|
||||||
if (['started', 'queued'].includes(statusData.status)) {
|
|
||||||
const progress = Math.round(statusData.progress) || '0';
|
|
||||||
button.text(`Cancel TF Annotation (${progress}%)`);
|
|
||||||
setTimeout(checkCallback, 5000);
|
|
||||||
} else {
|
|
||||||
button.text('Run TF Annotation');
|
|
||||||
button.removeClass('tfAnnotationProcess');
|
|
||||||
button.prop('disabled', false);
|
|
||||||
|
|
||||||
if (statusData.status === 'failed') {
|
|
||||||
const message = `Tensorflow annotation failed. Error: ${statusData.stderr}`;
|
|
||||||
showMessage(message);
|
|
||||||
} else if (statusData.status !== 'finished') {
|
|
||||||
const message = `Tensorflow annotation check request returned status "${statusData.status}"`;
|
|
||||||
showMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not sent tensorflow annotation check request. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(checkCallback, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function runProcess(tid, button) {
|
|
||||||
$.get(`/tensorflow/annotation/create/task/${tid}`).done(() => {
|
|
||||||
showMessage('Process has started');
|
|
||||||
button.text('Cancel TF Annotation (0%)');
|
|
||||||
button.addClass('tfAnnotationProcess');
|
|
||||||
checkProcess(tid, button);
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not run tf annotation. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function cancelProcess(tid, button) {
|
|
||||||
$.get(`/tensorflow/annotation/cancel/task/${tid}`).done(() => {
|
|
||||||
button.prop('disabled', true);
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not cancel tf annotation. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function setupDashboardItem(item, metaData) {
|
|
||||||
const tid = +item.attr('tid');
|
|
||||||
const button = $('<button> Run TF Annotation </button>');
|
|
||||||
|
|
||||||
button.on('click', () => {
|
|
||||||
if (button.hasClass('tfAnnotationProcess')) {
|
|
||||||
userConfirm('The process will be canceled. Continue?', () => {
|
|
||||||
cancelProcess(tid, button);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
userConfirm('The current annotation will be lost. Are you sure?', () => {
|
|
||||||
runProcess(tid, button);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
button.addClass('dashboardTFAnnotationButton regular dashboardButtonUI');
|
|
||||||
button.appendTo(item.find('div.dashboardButtonsUI'));
|
|
||||||
|
|
||||||
if ((tid in metaData) && (metaData[tid].active)) {
|
|
||||||
button.text('Cancel TF Annotation');
|
|
||||||
button.addClass('tfAnnotationProcess');
|
|
||||||
checkProcess(tid, button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = $('.dashboardItem');
|
|
||||||
const tids = Array.from(elements, el => +el.getAttribute('tid'));
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: '/tensorflow/annotation/meta/get',
|
|
||||||
data: JSON.stringify(tids),
|
|
||||||
contentType: 'application/json; charset=utf-8',
|
|
||||||
}).done((metaData) => {
|
|
||||||
elements.each(function setupDashboardItemWrapper() {
|
|
||||||
setupDashboardItem($(this), metaData);
|
|
||||||
});
|
|
||||||
}).fail((errorData) => {
|
|
||||||
const message = `Can not get tf annotation meta info. Code: ${errorData.status}. `
|
|
||||||
+ `Message: ${errorData.responseText || errorData.statusText}`;
|
|
||||||
showMessage(message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue