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