diff --git a/cvat/apps/dashboard/admin.py b/cvat/apps/dashboard/admin.py deleted file mode 100644 index af8dfc47525b..000000000000 --- a/cvat/apps/dashboard/admin.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.contrib import admin - -# Register your models here. - diff --git a/cvat/apps/dashboard/apps.py b/cvat/apps/dashboard/apps.py index 73611b409636..963627623751 100644 --- a/cvat/apps/dashboard/apps.py +++ b/cvat/apps/dashboard/apps.py @@ -5,7 +5,9 @@ from django.apps import AppConfig - class DashboardConfig(AppConfig): - name = 'dashboard' + name = 'cvat.apps.dashboard' + def ready(self): + # plugin registration + pass \ No newline at end of file diff --git a/cvat/apps/dashboard/models.py b/cvat/apps/dashboard/models.py deleted file mode 100644 index cdf3b0827bf1..000000000000 --- a/cvat/apps/dashboard/models.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.db import models - -# Create your models here. - diff --git a/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/pagination.css b/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/pagination.css new file mode 100644 index 000000000000..117bf0ba3e48 --- /dev/null +++ b/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/pagination.css @@ -0,0 +1 @@ +.paginationjs{line-height:1.6;font-family:Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,sans-serif;font-size:14px;box-sizing:initial}.paginationjs:after{display:table;content:" ";clear:both}.paginationjs .paginationjs-pages{float:left}.paginationjs .paginationjs-pages ul{float:left;margin:0;padding:0}.paginationjs .paginationjs-go-button,.paginationjs .paginationjs-go-input,.paginationjs .paginationjs-nav{float:left;margin-left:10px;font-size:14px}.paginationjs .paginationjs-pages li{float:left;border:1px solid #aaa;border-right:none;list-style:none}.paginationjs .paginationjs-pages li>a{min-width:30px;height:28px;line-height:28px;display:block;background:#fff;font-size:14px;color:#333;text-decoration:none;text-align:center}.paginationjs .paginationjs-pages li>a:hover{background:#eee}.paginationjs .paginationjs-pages li.active{border:none}.paginationjs .paginationjs-pages li.active>a{height:30px;line-height:30px;background:#aaa;color:#fff}.paginationjs .paginationjs-pages li.disabled>a{opacity:.3}.paginationjs .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs .paginationjs-pages li:first-child,.paginationjs .paginationjs-pages li:first-child>a{border-radius:3px 0 0 3px}.paginationjs .paginationjs-pages li:last-child{border-right:1px solid #aaa;border-radius:0 3px 3px 0}.paginationjs .paginationjs-pages li:last-child>a{border-radius:0 3px 3px 0}.paginationjs .paginationjs-go-input>input[type=text]{width:30px;height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;padding:0;font-size:14px;text-align:center;vertical-align:baseline;outline:0;box-shadow:none;box-sizing:initial}.paginationjs .paginationjs-go-button>input[type=button]{min-width:40px;height:30px;line-height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;text-align:center;padding:0 8px;font-size:14px;vertical-align:baseline;outline:0;box-shadow:none;color:#333;cursor:pointer;vertical-align:middle\9}.paginationjs.paginationjs-theme-blue .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-blue .paginationjs-pages li{border-color:#289de9}.paginationjs .paginationjs-go-button>input[type=button]:hover{background-color:#f8f8f8}.paginationjs .paginationjs-nav{height:30px;line-height:30px}.paginationjs .paginationjs-go-button,.paginationjs .paginationjs-go-input{margin-left:5px\9}.paginationjs.paginationjs-small{font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li>a{min-width:26px;height:24px;line-height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li.active>a{height:26px;line-height:26px}.paginationjs.paginationjs-small .paginationjs-go-input{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-input>input[type=text]{width:26px;height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button>input[type=button]{min-width:30px;height:26px;line-height:24px;padding:0 6px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-nav{height:26px;line-height:26px;font-size:12px}.paginationjs.paginationjs-big{font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li>a{min-width:36px;height:34px;line-height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li.active>a{height:36px;line-height:36px}.paginationjs.paginationjs-big .paginationjs-go-input{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{width:36px;height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button>input[type=button]{min-width:50px;height:36px;line-height:34px;padding:0 12px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-nav{height:36px;line-height:36px;font-size:16px}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a{color:#289de9}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a:hover{background:#e9f4fc}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.active>a{background:#289de9;color:#fff}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]{background:#289de9;border-color:#289de9;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-green .paginationjs-pages li{border-color:#449d44}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]:hover{background-color:#3ca5ea}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a{color:#449d44}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a:hover{background:#ebf4eb}.paginationjs.paginationjs-theme-green .paginationjs-pages li.active>a{background:#449d44;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]{background:#449d44;border-color:#449d44;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-yellow .paginationjs-pages li{border-color:#ec971f}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]:hover{background-color:#55a555}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a{color:#ec971f}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a:hover{background:#fdf5e9}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.active>a{background:#ec971f;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]{background:#ec971f;border-color:#ec971f;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-red .paginationjs-pages li{border-color:#c9302c}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]:hover{background-color:#eea135}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a{color:#c9302c}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a:hover{background:#faeaea}.paginationjs.paginationjs-theme-red .paginationjs-pages li.active>a{background:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]{background:#c9302c;border-color:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]:hover{background-color:#ce4541}.paginationjs .paginationjs-pages li.paginationjs-next{border-right:1px solid #aaa\9}.paginationjs .paginationjs-go-input>input[type=text]{line-height:28px\9;vertical-align:middle\9}.paginationjs.paginationjs-big .paginationjs-pages li>a{line-height:36px\9}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{height:36px\9;line-height:36px\9} \ No newline at end of file diff --git a/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/pagination.min.js b/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/pagination.min.js new file mode 100644 index 000000000000..a8472b590cef --- /dev/null +++ b/cvat/apps/dashboard/static/dashboard/js/3rdparty/pagination/pagination.min.js @@ -0,0 +1,11 @@ +/* + * pagination.js 2.1.4 + * A jQuery plugin to provide simple yet fully customisable pagination + * https://github.com/superRaytin/paginationjs + + * Homepage: http://pagination.js.org + * + * Copyright 2014-2100, superRaytin + * Released under the MIT license. +*/ +!function(a,b){function c(a){throw new Error("Pagination: "+a)}function d(a){a.dataSource||c('"dataSource" is required.'),"string"==typeof a.dataSource?void 0===a.totalNumberLocator?void 0===a.totalNumber?c('"totalNumber" is required.'):b.isNumeric(a.totalNumber)||c('"totalNumber" is incorrect. (Number)'):b.isFunction(a.totalNumberLocator)||c('"totalNumberLocator" should be a Function.'):i.isObject(a.dataSource)&&(void 0===a.locator?c('"dataSource" is an Object, please specify "locator".'):"string"==typeof a.locator||b.isFunction(a.locator)||c(a.locator+" is incorrect. (String | Function)")),void 0===a.formatResult||b.isFunction(a.formatResult)||c('"formatResult" should be a Function.')}function e(a){var c=["go","previous","next","disable","enable","refresh","show","hide","destroy"];b.each(c,function(b,c){a.off(h+c)}),a.data("pagination",{}),b(".paginationjs",a).remove()}function f(a,b){return("object"==(b=typeof a)?null==a&&"null"||Object.prototype.toString.call(a).slice(8,-1):b).toLowerCase()}void 0===b&&c("Pagination requires jQuery.");var g="pagination",h="__pagination-";b.fn.pagination&&(g="pagination2"),b.fn[g]=function(f){if(void 0===f)return this;var j=b(this),k=b.extend({},b.fn[g].defaults,f),l={initialize:function(){var a=this;if(j.data("pagination")||j.data("pagination",{}),!1!==a.callHook("beforeInit")){j.data("pagination").initialized&&b(".paginationjs",j).remove(),a.disabled=!!k.disabled;var c=a.model={pageRange:k.pageRange,pageSize:k.pageSize};a.parseDataSource(k.dataSource,function(b){if(a.isAsync=i.isString(b),i.isArray(b)&&(c.totalNumber=k.totalNumber=b.length),a.isDynamicTotalNumber=a.isAsync&&k.totalNumberLocator,!(k.hideWhenLessThanOnePage&&a.getTotalPage()<=1)){var d=a.render(!0);k.className&&d.addClass(k.className),c.el=d,j["bottom"===k.position?"append":"prepend"](d),a.observer(),j.data("pagination").initialized=!0,a.callHook("afterInit",d)}})}},render:function(a){var c=this,d=c.model,e=d.el||b('
'),f=!0!==a;c.callHook("beforeRender",f);var g=d.pageNumber||k.pageNumber,h=k.pageRange,i=c.getTotalPage(),j=g-h,l=g+h;return l>i&&(l=i,j=i-2*h,j=j<1?1:j),j<=1&&(j=1,l=Math.min(2*h+1,i)),e.html(c.generateHTML({currentPage:g,pageRange:h,rangeStart:j,rangeEnd:l})),c.callHook("afterRender",f),e},generateHTML:function(a){var c,d,e=this,f=a.currentPage,g=e.getTotalPage(),h=a.rangeStart,i=a.rangeEnd,j=e.getTotalNumber(),l=k.showPrevious,m=k.showNext,n=k.showPageNumbers,o=k.showNavigator,p=k.showGoInput,q=k.showGoButton,r=k.pageLink,s=k.prevText,t=k.nextText,u=k.ellipsisText,v=k.goButtonText,w=k.classPrefix,x=k.activeClassName,y=k.disableClassName,z=k.ulClassName,A="",B='',C='',D=b.isFunction(k.formatNavigator)?k.formatNavigator(f,g,j):k.formatNavigator,E=b.isFunction(k.formatGoInput)?k.formatGoInput(B,f,g,j):k.formatGoInput,F=b.isFunction(k.formatGoButton)?k.formatGoButton(C,f,g,j):k.formatGoButton,G=b.isFunction(k.autoHidePrevious)?k.autoHidePrevious():k.autoHidePrevious,H=b.isFunction(k.autoHideNext)?k.autoHideNext():k.autoHideNext,I=b.isFunction(k.header)?k.header(f,g,j):k.header,J=b.isFunction(k.footer)?k.footer(f,g,j):k.footer;if(I&&(c=e.replaceVariables(I,{currentPage:f,totalPage:g,totalNumber:j}),A+=c),l||n||m){if(A+='
',A+=z?'
"}return o&&D&&(c=e.replaceVariables(D,{currentPage:f,totalPage:g,totalNumber:j}),A+='
'+c+"
"),p&&E&&(c=e.replaceVariables(E,{currentPage:f,totalPage:g,totalNumber:j,input:B}),A+='
'+c+"
"),q&&F&&(c=e.replaceVariables(F,{currentPage:f,totalPage:g,totalNumber:j,button:C}),A+='
'+c+"
"),J&&(c=e.replaceVariables(J,{currentPage:f,totalPage:g,totalNumber:j}),A+=c),A},findTotalNumberFromRemoteResponse:function(a){this.model.totalNumber=k.totalNumberLocator(a)},go:function(a,c){function d(a){if(!1===e.callHook("beforePaging",g))return!1;if(f.direction=void 0===f.pageNumber?0:g>f.pageNumber?1:-1,f.pageNumber=g,e.render(),e.disabled&&e.isAsync&&e.enable(),j.data("pagination").model=f,k.formatResult){var d=b.extend(!0,[],a);i.isArray(a=k.formatResult(d))||(a=d)}j.data("pagination").currentPageData=a,e.doCallback(a,c),e.callHook("afterPaging",g),1==g&&e.callHook("afterIsFirstPage"),g==e.getTotalPage()&&e.callHook("afterIsLastPage")}var e=this,f=e.model;if(!e.disabled){var g=a;if((g=parseInt(g))&&!(g<1)){var h=k.pageSize,l=e.getTotalNumber(),m=e.getTotalPage();if(!(l>0&&g>m)){if(!e.isAsync)return void d(e.getDataFragment(g));var n={},o=k.alias||{};n[o.pageSize?o.pageSize:"pageSize"]=h,n[o.pageNumber?o.pageNumber:"pageNumber"]=g;var p=b.isFunction(k.ajax)?k.ajax():k.ajax,q={type:"get",cache:!1,data:{},contentType:"application/x-www-form-urlencoded; charset=UTF-8",dataType:"json",async:!0};b.extend(!0,q,p),b.extend(q.data,n),q.url=k.dataSource,q.success=function(a){e.isDynamicTotalNumber?e.findTotalNumberFromRemoteResponse(a):e.model.totalNumber=k.totalNumber,d(e.filterDataByLocator(a))},q.error=function(a,b,c){k.formatAjaxError&&k.formatAjaxError(a,b,c),e.enable()},e.disable(),b.ajax(q)}}}},doCallback:function(a,c){var d=this,e=d.model;b.isFunction(c)?c(a,e):b.isFunction(k.callback)&&k.callback(a,e)},destroy:function(){!1!==this.callHook("beforeDestroy")&&(this.model.el.remove(),j.off(),b("#paginationjs-style").remove(),this.callHook("afterDestroy"))},previous:function(a){this.go(this.model.pageNumber-1,a)},next:function(a){this.go(this.model.pageNumber+1,a)},disable:function(){var a=this,b=a.isAsync?"async":"sync";!1!==a.callHook("beforeDisable",b)&&(a.disabled=!0,a.model.disabled=!0,a.callHook("afterDisable",b))},enable:function(){var a=this,b=a.isAsync?"async":"sync";!1!==a.callHook("beforeEnable",b)&&(a.disabled=!1,a.model.disabled=!1,a.callHook("afterEnable",b))},refresh:function(a){this.go(this.model.pageNumber,a)},show:function(){var a=this;a.model.el.is(":visible")||a.model.el.show()},hide:function(){var a=this;a.model.el.is(":visible")&&a.model.el.hide()},replaceVariables:function(a,b){var c;for(var d in b){var e=b[d],f=new RegExp("<%=\\s*"+d+"\\s*%>","img");c=(c||a).replace(f,e)}return c},getDataFragment:function(a){var b=k.pageSize,c=k.dataSource,d=this.getTotalNumber(),e=b*(a-1)+1,f=Math.min(a*b,d);return c.slice(e-1,f)},getTotalNumber:function(){return this.model.totalNumber||k.totalNumber||0},getTotalPage:function(){return Math.ceil(this.getTotalNumber()/k.pageSize)},getLocator:function(a){var d;return"string"==typeof a?d=a:b.isFunction(a)?d=a():c('"locator" is incorrect. (String | Function)'),d},filterDataByLocator:function(a){var d,e=this.getLocator(k.locator);if(i.isObject(a)){try{b.each(e.split("."),function(b,c){d=(d||a)[c]})}catch(a){}d?i.isArray(d)||c("dataSource."+e+" must be an Array."):c("dataSource."+e+" is undefined.")}return d||a},parseDataSource:function(a,d){var e=this;i.isObject(a)?d(k.dataSource=e.filterDataByLocator(a)):i.isArray(a)?d(k.dataSource=a):b.isFunction(a)?k.dataSource(function(a){i.isArray(a)||c('The parameter of "done" Function should be an Array.'),e.parseDataSource.call(e,a,d)}):"string"==typeof a?(/^https?|file:/.test(a)&&(k.ajaxDataType="jsonp"),d(a)):c('Unexpected type of "dataSource".')},callHook:function(c){var d,e=j.data("pagination"),f=Array.prototype.slice.apply(arguments);return f.shift(),k[c]&&b.isFunction(k[c])&&!1===k[c].apply(a,f)&&(d=!1),e.hooks&&e.hooks[c]&&b.each(e.hooks[c],function(b,c){!1===c.apply(a,f)&&(d=!1)}),!1!==d},observer:function(){var a=this,d=a.model.el;j.on(h+"go",function(d,e,f){(e=parseInt(b.trim(e)))&&(b.isNumeric(e)||c('"pageNumber" is incorrect. (Number)'),a.go(e,f))}),d.delegate(".J-paginationjs-page","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));if(e&&!d.hasClass(k.disableClassName)&&!d.hasClass(k.activeClassName))return!1!==a.callHook("beforePageOnClick",c,e)&&(a.go(e),a.callHook("afterPageOnClick",c,e),!!k.pageLink&&void 0)}),d.delegate(".J-paginationjs-previous","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));if(e&&!d.hasClass(k.disableClassName))return!1!==a.callHook("beforePreviousOnClick",c,e)&&(a.go(e),a.callHook("afterPreviousOnClick",c,e),!!k.pageLink&&void 0)}),d.delegate(".J-paginationjs-next","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));if(e&&!d.hasClass(k.disableClassName))return!1!==a.callHook("beforeNextOnClick",c,e)&&(a.go(e),a.callHook("afterNextOnClick",c,e),!!k.pageLink&&void 0)}),d.delegate(".J-paginationjs-go-button","click",function(c){var e=b(".J-paginationjs-go-pagenumber",d).val();if(!1===a.callHook("beforeGoButtonOnClick",c,e))return!1;j.trigger(h+"go",e),a.callHook("afterGoButtonOnClick",c,e)}),d.delegate(".J-paginationjs-go-pagenumber","keyup",function(c){if(13===c.which){var e=b(c.currentTarget).val();if(!1===a.callHook("beforeGoInputOnEnter",c,e))return!1;j.trigger(h+"go",e),b(".J-paginationjs-go-pagenumber",d).focus(),a.callHook("afterGoInputOnEnter",c,e)}}),j.on(h+"previous",function(b,c){a.previous(c)}),j.on(h+"next",function(b,c){a.next(c)}),j.on(h+"disable",function(){a.disable()}),j.on(h+"enable",function(){a.enable()}),j.on(h+"refresh",function(b,c){a.refresh(c)}),j.on(h+"show",function(){a.show()}),j.on(h+"hide",function(){a.hide()}),j.on(h+"destroy",function(){a.destroy()});var e=Math.max(a.getTotalPage(),1),f=k.pageNumber;a.isDynamicTotalNumber&&(f=1),k.triggerPagingOnInit&&j.trigger(h+"go",Math.min(f,e))}};if(j.data("pagination")&&!0===j.data("pagination").initialized){if(b.isNumeric(f))return j.trigger.call(this,h+"go",f,arguments[1]),this;if("string"==typeof f){var m=Array.prototype.slice.apply(arguments);switch(m[0]=h+m[0],f){case"previous":case"next":case"go":case"disable":case"enable":case"refresh":case"show":case"hide":case"destroy":j.trigger.apply(this,m);break;case"getSelectedPageNum":return j.data("pagination").model?j.data("pagination").model.pageNumber:j.data("pagination").attributes.pageNumber;case"getTotalPage":return Math.ceil(j.data("pagination").model.totalNumber/j.data("pagination").model.pageSize);case"getSelectedPageData":return j.data("pagination").currentPageData;case"isDisabled":return!0===j.data("pagination").model.disabled;default:c("Unknown action: "+f)}return this}e(j)}else i.isObject(f)||c("Illegal options");return d(k),l.initialize(),this},b.fn[g].defaults={totalNumber:0,pageNumber:1,pageSize:10,pageRange:2,showPrevious:!0,showNext:!0,showPageNumbers:!0,showNavigator:!1,showGoInput:!1,showGoButton:!1,pageLink:"",prevText:"«",nextText:"»",ellipsisText:"...",goButtonText:"Go",classPrefix:"paginationjs",activeClassName:"active",disableClassName:"disabled",inlineStyle:!0,formatNavigator:"<%= currentPage %> / <%= totalPage %>",formatGoInput:"<%= input %>",formatGoButton:"<%= button %>",position:"bottom",autoHidePrevious:!1,autoHideNext:!1,triggerPagingOnInit:!0,hideWhenLessThanOnePage:!1,showFirstOnEllipsisShow:!0,showLastOnEllipsisShow:!0,callback:function(){}},b.fn.addHook=function(a,d){arguments.length<2&&c("Missing argument."),b.isFunction(d)||c("callback must be a function.");var e=b(this),f=e.data("pagination");f||(e.data("pagination",{}),f=e.data("pagination")),!f.hooks&&(f.hooks={}),f.hooks[a]=f.hooks[a]||[],f.hooks[a].push(d)},b[g]=function(a,d){arguments.length<2&&c("Requires two parameters.");var e;if(e="string"!=typeof a&&a instanceof jQuery?a:b(a),e.length)return e.pagination(d),e};var i={};b.each(["Object","Array","String"],function(a,b){i["is"+b]=function(a){return f(a)===b.toLowerCase()}}),"function"==typeof define&&define.amd&&define(function(){return b})}(this,window.jQuery); \ No newline at end of file diff --git a/cvat/apps/dashboard/static/dashboard/js/dashboard.js b/cvat/apps/dashboard/static/dashboard/js/dashboard.js index a79b5be91538..61da0d767c54 100644 --- a/cvat/apps/dashboard/static/dashboard/js/dashboard.js +++ b/cvat/apps/dashboard/static/dashboard/js/dashboard.js @@ -6,185 +6,123 @@ /* global AnnotationParser:false - Config:false userConfirm:false ConstIdGenerator:false createExportContainer:false - dumpAnnotationRequest:false + dumpAnnotationRequest: false + dumpAnnotation:false LabelsInfo:false showMessage:false showOverlay:false */ -"use strict"; -/* Server requests */ -function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, onComplete, onUpdateStatus) { - $.ajax({ - url: "/api/v1/tasks/", - type: "POST", - data: oData, - contentType: false, - processData: false, - success: function(data) { - onSuccessRequest(); - requestCreatingStatus(data); - }, - error: function(data) { - onComplete(); - onError(data.responseText); - } - }); +'use strict'; - function requestCreatingStatus(task) { - let tid = task.id; - let request_frequency_ms = 1000; - let done = false; +class TaskView { + constructor(details, ondelete, onupdate) { + this.init(details); - let requestInterval = setInterval(function() { - $.ajax({ - url: `/api/v1/tasks/${tid}/status`, - success: receiveStatus, - error: function(data) { - clearInterval(requestInterval); - onComplete(); - onError(data.responseText); - } - }); - }, request_frequency_ms); - - function receiveStatus(data) { - if (done) return; - if (data.state === "Finished") { - done = true; - clearInterval(requestInterval); - onComplete(); - onSuccessCreate(tid); - } - else if (data.state === "Failed") { - done = true; - clearInterval(requestInterval); - onComplete(); - onError(data.message); - } - else if (data["state"] === "Started" && data.message != "") { - onUpdateStatus(data.message); - } - } + this._ondelete = ondelete; + this._onupdate = onupdate; + this._UI = null; } -} - -function updateTaskRequest(labels) { - let data = new LabelsInfo(labels); - - $.ajax({ - url: "/api/v1/tasks/" + window.cvat.dashboard.taskID, - type: "PATCH", - data: data, - contentType: false, - processData: false, - success: function() { - $("#dashboardNewLabels").prop("value", ""); - showMessage("Task successfully updated."); - }, - error: function(data) { - showMessage("Task update error. " + data.responseText); - }, - complete: () => $("#dashboardUpdateModal").addClass("hidden") - }); -} - - -function removeTaskRequest() { - userConfirm("The action can not be undone. Are you sure?", confirmCallback); + _disable() { + this._UI.find('*').attr('disabled', true).css('opacity', 0.5); + this._UI.find('.dashboardJobList').empty(); + } - function confirmCallback() { + _remove() { $.ajax ({ - url: "/delete/task/" + window.cvat.dashboard.taskID, - success: function() { - $(`#dashboardTask_${window.cvat.dashboard.taskID}`).remove(); - showMessage("Task removed."); + url: `/api/v1/tasks/${this._id}`, + type: 'DELETE', + success: () => { + this._ondelete(this._id); + this._disable(); }, - error: function(response) { - let message = "Abort. Reason: " + response.responseText; + error: (errorData) => { + const message = `Can not build CVAT dashboard. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; showMessage(message); - throw Error(message); } }); } -} + _update() { + $('#dashboardUpdateModal').remove(); + const dashboardUpdateModal = $($('#dashboardUpdateTemplate').html()).appendTo('body'); + $('#dashboardOldLabels').prop('value', LabelsInfo.serialize(this._labels)); -function uploadAnnotationRequest() { - let input = $("").attr({ - type: "file", - accept: "text/xml" - }).on("change", loadXML).click(); + $('#dashboardCancelUpdate').on('click', () => { + dashboardUpdateModal.remove(); + }); - function loadXML(e) { - input.remove(); - let overlay = showOverlay("File is being uploaded.."); - let file = e.target.files[0]; - let fileReader = new FileReader(); - fileReader.onload = (e) => parseFile(e, overlay); - fileReader.readAsText(file); + $('#dashboardSubmitUpdate').on('click', () => { + try { + const body = { + labels: LabelsInfo.deserialize($('#dashboardNewLabels').prop('value')) + }; + $.ajax({ + url: `/api/v1/tasks/${this._id}`, + type: 'PATCH', + contentType: 'application/json', + data: JSON.stringify(body) + }).done(() => { + this._onupdate(); + dashboardUpdateModal.remove(); + showMessage('Task has been successfully updated'); + }).fail((errorData) => { + const message = `Can not build CVAT dashboard. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; + showMessage(message); + }); + } catch (exception) { + showMessage(exception); + } + }); } - function parseFile(e, overlay) { - let xmlText = e.target.result; - overlay.setMessage("Request task data from server.."); - $.when( - $.get("/api/v1/tasks/" + window.cvat.dashboard.taskID), - $.get("/api/v1/tasks/" + window.cvat.dashboard.taskID + "/frames/meta"), - ).then( - function(taskInfo, imageMetaCache) { - let spec = {"labels": {}, "attributes": {}}; - for (let label of taskInfo[0].labels) { - spec.labels[label.id] = label.name; - spec.attributes[label.id] = {}; - for (let attr of label.attributes) { - spec.attributes[label.id][attr.id] = attr.text; - } - } - let annotationParser = new AnnotationParser( - { - start: 0, - stop: taskInfo[0].size, - image_meta_data: imageMetaCache[0], - flipped: taskInfo[0].flipped - }, - new LabelsInfo(spec), - new ConstIdGenerator(-1) - ); + _upload(tid) { + function parse(overlay, e) { + const xmlText = e.target.result; + $.get(`/api/v1/tasks/${tid}/frames/meta`).done((imageMetaCache) => { + const labelsCopy = JSON.parse(JSON.stringify(this._labels)); + + const parser = new AnnotationParser({ + start: 0, + stop: this._size, + flipped: this._flipped, + image_meta_data: imageMetaCache, + }, new LabelsInfo({labels: {}, attributes: {}}).restConstructor(labelsCopy), new ConstIdGenerator(-1)); - let asyncParse = function() { + function asyncParse() { let parsed = null; try { - parsed = annotationParser.parse(xmlText); + parsed = parser.parse(xmlText); } catch(error) { overlay.remove(); - showMessage("Parsing errors was occurred. " + error); + showMessage('Parsing errors occured. ' + error); return; } - let asyncSave = function() { + function asyncSave() { $.ajax({ - url: "/delete/annotation/task/" + window.cvat.dashboard.taskID, - type: "DELETE", + // TODO: Use REST API + url: '/delete/annotation/task/' + window.cvat.dashboard.taskID, + type: 'DELETE', success: function() { asyncSaveChunk(0); }, error: function(response) { - let message = "Previous annotations cannot be deleted: " + - response.responseText; + const message = `Could not remove current annotation. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; showMessage(message); - overlay.remove(); }, }); }; - let asyncSaveChunk = function(start) { + function asyncSaveChunk(start) { const CHUNK_SIZE = 100000; let end = start + CHUNK_SIZE; let chunk = {}; @@ -197,479 +135,580 @@ function uploadAnnotationRequest() { } if (next) { - let exportData = createExportContainer(); + const exportData = createExportContainer(); exportData.create = chunk; $.ajax({ - url: "/save/annotation/task/" + window.cvat.dashboard.taskID, - type: "POST", + // TODO: Use REST API + url: `/save/annotation/task/${tid}`, + type: 'POST', data: JSON.stringify(exportData), - contentType: "application/json", - success: function() { - asyncSaveChunk(end); - }, - error: function(response) { - let message = "Annotations uploading errors were occurred: " + - response.responseText; - showMessage(message); - overlay.remove(); - }, + contentType: 'application/json', + }).done(() => { + asyncSaveChunk(end); + }).fail((errorData) => { + const message = `Annotation uploading errors occurred. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; + showMessage(message); }); } else { - let message = "Annotations were uploaded successfully"; + const message = 'Annotation have been successfully uploaded'; showMessage(message); overlay.remove(); } - }; + } - overlay.setMessage("Annotation is being saved.."); + overlay.setMessage('The annotation is being saved..'); setTimeout(asyncSave); - }; + } - overlay.setMessage("File is being parsed.."); + overlay.setMessage('The annotation file is being parsed..'); setTimeout(asyncParse); - }, - function(response) { - overlay.remove(); - let message = "Bad task request: " + response.responseText; + }).fail((errorData) => { + const message = `Can not get required data from the server. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; showMessage(message); - throw Error(message); + }); + + overlay.setMessage('Required data are being downloaded from the server..'); + } + + self = this; + $('').on('change', function() { + const file = this.files[0]; + $(this).remove(); + if (file) { + const fileReader = new FileReader(); + fileReader.onload = parse.bind(self, overlay); + fileReader.readAsText(file); } - ); + }).click(); } -} + _dump(button) { + dumpAnnotationRequest(button, this._id, this._name); + } -/* Dashboard entrypoint */ -window.cvat = window.cvat || {}; -window.cvat.dashboard = window.cvat.dashboard || {}; -window.cvat.dashboard.uiCallbacks = window.cvat.dashboard.uiCallbacks || []; -window.cvat.config = new Config(); - -window.cvat.dashboard.uiCallbacks.push(function(elements) { - elements.each(function(idx) { - let elem = $(elements[idx]); - let taskID = +elem.attr("id").split("_")[1]; - let taskName = $.trim($( elem.find("label.dashboardTaskNameLabel")[0] ).text()); - let buttonsUI = elem.find("div.dashboardButtonsUI")[0]; - - let dumpButton = $( $(buttonsUI).find("button.dashboardDumpAnnotation")[0] ); - let uploadButton = $( $(buttonsUI).find("button.dashboardUploadAnnotation")[0] ); - let updateButton = $( $(buttonsUI).find("button.dashboardUpdateTask")[0] ); - let deleteButton = $( $(buttonsUI).find("button.dashboardDeleteTask")[0] ); - - let bugTrackerButton = $(buttonsUI).find(".dashboardOpenTrackerButton"); - if (bugTrackerButton.length) { - bugTrackerButton = $(bugTrackerButton[0]); - bugTrackerButton.on("click", function() { - window.open($(buttonsUI).find("a.dashboardBugTrackerLink").attr("href")); - }); + init(details) { + for (let prop in details) { + this[`_${prop}`] = details[prop]; } + } - dumpButton.on("click", function() { - window.cvat.dashboard.taskID = taskID; - window.cvat.dashboard.taskName = taskName; - dumpAnnotationRequest(dumpButton, taskID, taskName); - }); - - uploadButton.on("click", function() { - window.cvat.dashboard.taskID = taskID; - window.cvat.dashboard.taskName = taskName; - userConfirm("The current annotation will be lost. Are you sure?", uploadAnnotationRequest); - }); + render(baseURL) { + const self = this; + this._UI = $('
').append( + $(`
+ +
`) + ).append( + $(`
+ +
`) + ).append( + $('
').css({ + 'background-image': `url("/api/v1/tasks/${this._id}/frames/0")` + }) + ); - updateButton.on("click", function() { - window.cvat.dashboard.taskID = taskID; - window.cvat.dashboard.taskName = taskName; - $("#dashboardUpdateModal").removeClass("hidden"); - $("#dashboardUpdateModal")[0].loadCurrentLabels(); - }); - deleteButton.on("click", function() { - window.cvat.dashboard.taskID = taskID; - window.cvat.dashboard.taskName = taskName; - removeTaskRequest(); - }); - }); -}); + const buttonsContainer = $(`
`).appendTo(this._UI); + $('').on('click', (e) => { + self._dump(e.target); + }).appendTo(buttonsContainer); -document.addEventListener("DOMContentLoaded", buildDashboard); + $('').on('click', () => { + userConfirm("The current annotation will be lost. Are you sure?", () => self._upload()); + }).appendTo(buttonsContainer); + $('').on('click', () => { + self._update(); + }).appendTo(buttonsContainer); -function buildDashboard() { - /* Setup static content */ - setupTaskCreator(); - setupTaskUpdater(); - setupSearch(); + $('').on('click', () => { + userConfirm("The task will be removed. Are you sure?", () => self._remove()); + }).appendTo(buttonsContainer); - $(window).on("click", function(event) { - if (event.target.classList.contains("modal")) { - event.target.classList.add("hidden"); + if (this._bug_tracker) { + $('').on('click', () => { + window.open(this._bug_tracker); + }).appendTo(buttonsContainer); } - }); - /* Setup task UIs */ - for (let callback of window.cvat.dashboard.uiCallbacks) { - callback( $(".dashboardTaskUI") ); - } - $("#loadingOverlay").remove(); -} + const jobsContainer = $(``); + for (let segment of this._segments) { + for (let job of segment.jobs) { + const link = `${baseURL}?id=${job.id}`; + jobsContainer.append($(``)); + } + } + this._UI.append($(` +
+
+ +
+
+ `).append(jobsContainer)); -function setupTaskCreator() { - let dashboardCreateTaskButton = $("#dashboardCreateTaskButton"); - let createModal = $("#dashboardCreateModal"); - let nameInput = $("#dashboardNameInput"); - let labelsInput = $("#dashboardLabelsInput"); - let bugTrackerInput = $("#dashboardBugTrackerInput"); - let localSourceRadio = $("#dashboardLocalSource"); - let shareSourceRadio = $("#dashboardShareSource"); - let selectFiles = $("#dashboardSelectFiles"); - let filesLabel = $("#dashboardFilesLabel"); - let localFileSelector = $("#dashboardLocalFileSelector"); - let shareFileSelector = $("#dashboardShareBrowseModal"); - let shareBrowseTree = $("#dashboardShareBrowser"); - let cancelBrowseServer = $("#dashboardCancelBrowseServer"); - let submitBrowseServer = $("#dashboardSubmitBrowseServer"); - let flipImagesBox = $("#dashboardFlipImages"); - let zOrderBox = $("#dashboardZOrder"); - let segmentSizeInput = $("#dashboardSegmentSize"); - let customSegmentSize = $("#dashboardCustomSegment"); - let overlapSizeInput = $("#dashboardOverlap"); - let customOverlapSize = $("#dashboardCustomOverlap"); - let imageQualityInput = $("#dashboardImageQuality"); - let customCompressQuality = $("#dashboardCustomQuality"); - - let taskMessage = $("#dashboardCreateTaskMessage"); - let submitCreate = $("#dashboardSubmitTask"); - let cancelCreate = $("#dashboardCancelTask"); - - let name = nameInput.prop("value"); - let labels = labelsInput.prop("value"); - let bugTrackerLink = bugTrackerInput.prop("value"); - let source = "local"; - let flipImages = false; - let zOrder = false; - let segmentSize = 5000; - let overlapSize = 0; - let compressQuality = 50; - let files = []; - - dashboardCreateTaskButton.on("click", function() { - $("#dashboardCreateModal").removeClass("hidden"); - }); + if (this._removed) { + this._disable(); + } - nameInput.on("change", (e) => {name = e.target.value;}); - bugTrackerInput.on("change", (e) => {bugTrackerLink = e.target.value;}); - labelsInput.on("change", (e) => {labels = e.target.value;}); + return this._UI; + } +} - localSourceRadio.on("click", function() { - if (source === "local") { - return; - } - source = "local"; - files = []; - updateSelectedFiles(); - }); - shareSourceRadio.on("click", function() { - if (source === "share") { - return; - } - source = "share"; - files = []; - updateSelectedFiles(); - }); +class DashboardView { + constructor(metaData, taskData) { + 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; - selectFiles.on("click", function() { - if (source === "local") { - localFileSelector.click(); - } - else { - shareBrowseTree.jstree("refresh"); - shareFileSelector.removeClass("hidden"); - shareBrowseTree.jstree({ - core: { - data: { - url: "get_share_nodes", - data: (node) => { return {"id" : node.id}; } - } - }, - plugins: ["checkbox", "sort"], - }); - } - }); + this._setupList(); + this._setupTaskSearch(); + this._setupCreateDialog(); + } - localFileSelector.on("change", function(e) { - files = e.target.files; - updateSelectedFiles(); - }); + _setupList() { + const dashboardList = $('#dashboardList'); + const dashboardPagination = $('#dashboardPagination'); + + const baseURL = this._baseURL; + dashboardPagination.pagination({ + dataSource: this._dashboardList, + callback: function(pageList) { + dashboardList.empty(); + for (let details of pageList) { + const detailsCopy = JSON.parse(JSON.stringify(details)); + const taskView = new TaskView(detailsCopy, () => { + // on delete task callback + details.removed = true + }, () => { + // on update task callback + $.get(`/api/v1/tasks/${details.id}`).done((taskData) => { + Object.assign(details, taskData); + taskView.init(details); + }).fail((errorData) => { + const message = `Can not get task from server. Showed info may be obsolete. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; + showMessage(message); + }) + }); + dashboardList.append(taskView.render(baseURL)); + } + const pages = $('.paginationjs-pages'); + pages.css('margin-left', (window.screen.width - pages.width()) / 2); + } + }); + } - cancelBrowseServer.on("click", () => shareFileSelector.addClass("hidden")); - submitBrowseServer.on("click", function() { - if (!createModal.hasClass("hidden")) { - files = shareBrowseTree.jstree(true).get_selected(); - cancelBrowseServer.click(); - updateSelectedFiles(); + _setupTaskSearch() { + function getUrlParameter(name) { + let regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); + let results = regex.exec(window.location.search); + return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); } - }); - flipImagesBox.on("click", (e) => { - flipImages = e.target.checked; - }); - - 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)); - - segmentSizeInput.on("change", function() { - let value = Math.clamp( - +segmentSizeInput.prop("value"), - +segmentSizeInput.prop("min"), - +segmentSizeInput.prop("max") - ); + let searchInput = $('#dashboardSearchInput'); + let searchSubmit = $('#dashboardSearchSubmit'); - segmentSizeInput.prop("value", value); - segmentSize = value; - }); + let line = getUrlParameter('search') || ''; + searchInput.val(line); - overlapSizeInput.on("change", function() { - let value = Math.clamp( - +overlapSizeInput.prop("value"), - +overlapSizeInput.prop("min"), - +overlapSizeInput.prop("max") - ); + searchSubmit.on('click', function() { + let e = $.Event('keypress'); + e.keyCode = 13; + searchInput.trigger(e); + }); - overlapSizeInput.prop("value", value); - overlapSize = value; - }); + searchInput.on('keypress', function(e) { + if (e.keyCode != 13) return; + let filter = e.target.value; + if (!filter) window.location.search = ''; + else window.location.search = `search=${filter}`; + }); + } - imageQualityInput.on("change", function() { - let value = Math.clamp( - +imageQualityInput.prop("value"), - +imageQualityInput.prop("min"), - +imageQualityInput.prop("max") - ); + _setupCreateDialog() { + 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'); + } + } - imageQualityInput.prop("value", value); - compressQuality = value; - }); - submitCreate.on("click", function() { - if (!validateName(name)) { - taskMessage.css("color", "red"); - taskMessage.text("Invalid task name"); - return; + function validateName(name) { + const math = name.match('[a-zA-Z0-9_]+'); + return math != null; } - if (!validateLabels(labels)) { - taskMessage.css("color", "red"); - taskMessage.text("Invalid task labels"); - return; + function validateLabels(labels) { + try { + LabelsInfo.deserialize(labels) + return true; + } catch { + return false; + } } - if (!validateSegmentSize(segmentSize)) { - taskMessage.css("color", "red"); - taskMessage.text("Segment size out of range"); - return; + function validateSegmentSize(segmentSize) { + return (segmentSize >= 100 && segmentSize <= 50000); } - if (!validateOverlapSize(overlapSize, segmentSize)) { - taskMessage.css("color", "red"); - taskMessage.text("Overlap size must be positive and not more then segment size"); - return; + function validateOverlapSize(overlapSize, segmentSize) { + return (overlapSize >= 0 && overlapSize <= segmentSize - 1); } - if (files.length <= 0) { - taskMessage.css("color", "red"); - taskMessage.text("Need specify files for task"); - return; - } - else if (files.length > window.maxUploadCount && source === "local") { - taskMessage.css("color", "red"); - taskMessage.text("Too many files. Please use share functionality"); - return; + function requestCreatingStatus(tid, onUpdateStatus, onSuccess, onError) { + function checkCallback() { + $.get(`/api/v1/tasks/${tid}/status`).done((data) => { + if (['Queued', 'Started'].includes(data.state)) { + if (data.message != '') { + onUpdateStatus(data.message); + } + setTimeout(checkCallback, 1000); + } else { + if (data.state === 'Finished') { + onSuccess(tid); + } + else if (data.state === 'Failed') { + const message = `Can not create task. ${data.message}`; + onError(message); + } else { + const message = `Unknown state has been received: ${data.state}`; + onError(message); + } + + } + }).fail((errorData) => { + const message = `Can not check task status. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; + onError(message); + }); + } + + setTimeout(checkCallback, 1000); } - else if (source === "local") { - let commonSize = 0; - for (let file of files) { - commonSize += file.size; + + const dashboardCreateTaskButton = $('#dashboardCreateTaskButton'); + const createModal = $('#dashboardCreateModal'); + const nameInput = $('#dashboardNameInput'); + const labelsInput = $('#dashboardLabelsInput'); + const bugTrackerInput = $('#dashboardBugTrackerInput'); + const localSourceRadio = $('#dashboardLocalSource'); + const shareSourceRadio = $('#dashboardShareSource'); + const selectFiles = $('#dashboardSelectFiles'); + const filesLabel = $('#dashboardFilesLabel'); + const localFileSelector = $('#dashboardLocalFileSelector'); + const shareFileSelector = $('#dashboardShareBrowseModal'); + const shareBrowseTree = $('#dashboardShareBrowser'); + const cancelBrowseServer = $('#dashboardCancelBrowseServer'); + const submitBrowseServer = $('#dashboardSubmitBrowseServer'); + const flipImagesBox = $('#dashboardFlipImages'); + const zOrderBox = $('#dashboardZOrder'); + const segmentSizeInput = $('#dashboardSegmentSize'); + const customSegmentSize = $('#dashboardCustomSegment'); + const overlapSizeInput = $('#dashboardOverlap'); + const customOverlapSize = $('#dashboardCustomOverlap'); + const imageQualityInput = $('#dashboardImageQuality'); + const customCompressQuality = $('#dashboardCustomQuality'); + + 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 flipImages = false; + let zOrder = false; + let segmentSize = 5000; + let overlapSize = 0; + let compressQuality = 50; + let files = []; + + 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 (commonSize > window.maxUploadSize) { - taskMessage.css("color", "red"); - taskMessage.text("Too big size. Please use share functionality"); + source = 'local'; + files = []; + updateSelectedFiles(); + }); + + shareSourceRadio.on('click', () => { + if (source === 'share') { return; } - } + source = 'share'; + files = []; + updateSelectedFiles(); + }); - let taskData = new FormData(); - taskData.append("name", name); - taskData.append("bug_tracker", bugTrackerLink); - // FIXME: a trivial parser for labels - labels = labels.split(/\s+/); - let labelObjs = []; - for (let labelIdx = 0; labelIdx < labels.length; labelIdx++) { - let label = labels[labelIdx] - let attributes = []; - while (labelIdx + 1 < labels.length) { - if (labels[labelIdx + 1].startsWith("~") || - labels[labelIdx + 1].startsWith("@")) { - attributes.push({"text" : labels[++labelIdx]}); - } else { - break; - } + selectFiles.on('click', () => { + if (source === 'local') { + localFileSelector.click(); } + else { + shareBrowseTree.jstree('refresh'); + shareFileSelector.removeClass('hidden'); + shareBrowseTree.jstree({ + core: { + data: { + url: 'get_share_nodes', + data: (node) => { return {'id' : node.id}; } + } + }, + plugins: ['checkbox', 'sort'], + }); + } + }); - labelObjs.push(JSON.stringify( - {"name": label, "attributes": attributes })); - } - for (let i = 0; i < labelObjs.length; i++) { - taskData.append(`labels[${i}]`, labelObjs[i]); - } + localFileSelector.on('change', function(e) { + files = e.target.files; + updateSelectedFiles(); + }); - taskData.append("flipped", flipImages); - taskData.append("z_order", zOrder); + cancelBrowseServer.on('click', () => shareFileSelector.addClass('hidden')); + submitBrowseServer.on('click', () => { + if (!createModal.hasClass('hidden')) { + files = shareBrowseTree.jstree(true).get_selected(); + cancelBrowseServer.click(); + updateSelectedFiles(); + } + }); - if (customSegmentSize.prop("checked")) { - taskData.append("segment_size", segmentSize); - } - if (customOverlapSize.prop("checked")) { - taskData.append("overlap", overlapSize); - } - if (customCompressQuality.prop("checked")) { - taskData.append("image_quality", compressQuality); - } + flipImagesBox.on('click', (e) => { + flipImages = e.target.checked; + }); - for (let j = 0; j < files.length; j++) { - if (source === "local") { - taskData.append(`client_files[${j}]`, files[j]); - } else { - taskData.append(`server_files[${j}]`, files[j]); - } - } + zOrderBox.on('click', (e) => { + zOrder = e.target.checked; + }); - submitCreate.prop("disabled", true); - createTaskRequest(taskData, - () => { - taskMessage.css("color", "green"); - taskMessage.text("Successful request! Creating.."); - }, - () => window.location.reload(), - (response) => { - taskMessage.css("color", "red"); - taskMessage.text(response); - }, - () => submitCreate.prop("disabled", false), - (status) => { - taskMessage.css("color", "blue"); - taskMessage.text(status); - }); - }); + 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)); - 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"); - } - } + segmentSizeInput.on('change', () => { + const value = Math.clamp( + +segmentSizeInput.prop('value'), + +segmentSizeInput.prop('min'), + +segmentSizeInput.prop('max') + ); + segmentSizeInput.prop('value', value); + segmentSize = value; + }); - function validateName(name) { - let math = name.match("[a-zA-Z0-9()_ ]+"); - return math != null; - } + overlapSizeInput.on('change', () => { + const value = Math.clamp( + +overlapSizeInput.prop('value'), + +overlapSizeInput.prop('min'), + +overlapSizeInput.prop('max') + ); - function validateLabels(labels) { - let tmp = labels.replace(/\s/g,""); - return tmp.length > 0; - // to do good validator - } + overlapSizeInput.prop('value', value); + overlapSize = value; + }); - function validateSegmentSize(segmentSize) { - return (segmentSize >= 100 && segmentSize <= 50000); - } + imageQualityInput.on('change', () => { + const value = Math.clamp( + +imageQualityInput.prop('value'), + +imageQualityInput.prop('min'), + +imageQualityInput.prop('max') + ); - function validateOverlapSize(overlapSize, segmentSize) { - return (overlapSize >= 0 && overlapSize <= segmentSize - 1); - } + imageQualityInput.prop('value', value); + compressQuality = value; + }); - cancelCreate.on("click", () => createModal.addClass("hidden")); -} + submitCreate.on('click', () => { + if (!validateName(name)) { + taskMessage.css('color', 'red'); + taskMessage.text('Bad task name'); + return; + } + if (!validateLabels(labels)) { + taskMessage.css('color', 'red'); + taskMessage.text('Bad labels specification'); + return; + } -function setupTaskUpdater() { - let updateModal = $("#dashboardUpdateModal"); - let oldLabels = $("#dashboardOldLabels"); - let newLabels = $("#dashboardNewLabels"); - let submitUpdate = $("#dashboardSubmitUpdate"); - let cancelUpdate = $("#dashboardCancelUpdate"); - - updateModal[0].loadCurrentLabels = function() { - $.ajax({ - url: "/api/v1/tasks/" + window.cvat.dashboard.taskID, - success: function(taskInfo) { - let spec = {"labels": {}, "attributes": {}}; - for (let label of taskInfo.labels) { - spec.labels[label.id] = label.name; - spec.attributes[label.id] = {}; - for (let attr of label.attributes) { - spec.attributes[label.id][attr.id] = attr.text; - } + if (!validateSegmentSize(segmentSize)) { + taskMessage.css('color', 'red'); + taskMessage.text('Segment size out of range'); + return; + } + + if (!validateOverlapSize(overlapSize, segmentSize)) { + taskMessage.css('color', 'red'); + taskMessage.text('Overlap size must be positive and not more then segment size'); + return; + } + + if (files.length <= 0) { + taskMessage.css('color', 'red'); + taskMessage.text('No files specified for the task'); + return; + } + else if (files.length > window.maxUploadCount && source === 'local') { + taskMessage.css('color', 'red'); + taskMessage.text('Too many files were specified. Please use share to upload'); + return; + } + else if (source === 'local') { + let commonSize = 0; + for (let 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; } - let labels = new LabelsInfo(spec); - oldLabels.attr("value", labels.normalize()); - }, - error: function(response) { - oldLabels.attr("value", "Bad request"); - let message = "Bad task request: " + response.responseText; - throw Error(message); } - }); - }; - cancelUpdate.on("click", function() { - $("#dashboardNewLabels").prop("value", ""); - updateModal.addClass("hidden"); - }); + const description = { + name: name, + labels: LabelsInfo.deserialize(labels), + image_quality: compressQuality + } - submitUpdate.on("click", () => updateTaskRequest(newLabels.prop("value"))); -} + if (bugTrackerLink) { + description.bug_tracker = bugTrackerLink; + } + if (flipImages) { + description.flipped = flipImages; + } + if (zOrder) { + description.z_order = zOrder; + } + if (customSegmentSize.prop('checked')) { + description.segment_size = segmentSize; + } + if (customOverlapSize.prop('checked')) { + description.overlap = overlapSize; + } + function cleanupTask(tid) { + $.ajax({ + url: `/api/v1/tasks/${tid}`, + type: 'DELETE' + }); + } -function setupSearch() { - function getUrlParameter(name) { - let regex = new RegExp("[\\?&]" + name + "=([^&#]*)"); - let results = regex.exec(window.location.search); - return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); - } + submitCreate.prop('disabled', true); + $.ajax({ + url: '/api/v1/tasks', + type: 'POST', + data: JSON.stringify(description), + contentType: 'application/json' + }).done((data) => { + taskMessage.css('color', 'green'); + taskMessage.text('Task has been created. Uploading the data..'); + + const batchOfFiles = new FormData(); + for (let j = 0; j < files.length; j++) { + if (source === "local") { + batchOfFiles.append(`client_files[${j}]`, files[j]); + } else { + batchOfFiles.append(`server_files[${j}]`, files[j]); + } + } - let searchInput = $("#dashboardSearchInput"); - let searchSubmit = $("#dashboardSearchSubmit"); + $.ajax({ + url: `/api/v1/tasks/${data.id}/data`, + type: 'POST', + data: batchOfFiles, + contentType: false, + processData: false + }).done(() => { + taskMessage.text('The data has been sent. Task is being created..'); + + requestCreatingStatus(data.id, (status) => { + taskMessage.css('color', 'blue'); + taskMessage.text(status); + }, () => { + window.location.reload(); + }, (errorMessage) => { + submitCreate.prop('disabled', false); + taskMessage.css('color', 'red'); + taskMessage.text(errorMessage); + cleanupTask(data.id); + }); + }).fail((errorData) => { + const message = `Can not put the data for the task. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; + taskMessage.css('color', 'red'); + taskMessage.text(message); + submitCreate.prop('disabled', false); + cleanupTask(data.id); + }); + }).fail((errorData) => { + const message = `Task has not been created. Code: ${errorData.status}. ` + + `Message: ${errorData.responseText || errorData.statusText}`; + taskMessage.css('color', 'red'); + taskMessage.text(message); + submitCreate.prop('disabled', false); + cleanupTask(data.id); + }); + }); - let line = getUrlParameter("search") || ""; - searchInput.val(line); + cancelCreate.on('click', () => createModal.addClass('hidden')); + } +} - searchSubmit.on("click", function() { - let e = $.Event("keypress"); - e.keyCode = 13; - searchInput.trigger(e); - }); - searchInput.on("keypress", function(e) { - if (e.keyCode != 13) return; - let filter = e.target.value; - if (!filter) window.location.search = ""; - else window.location.search = `search=${filter}`; +// DASHBOARD ENTRYPOINT +window.addEventListener('DOMContentLoaded', () => { + $.when( + // TODO: Use REST API in order to get meta + $.get('/dashboard/meta'), + $.get(`/api/v1/tasks${window.location.search}`), + ).then((metaData, taskData) => { + try { + new DashboardView(metaData[0], taskData[0]); + $(window).on('click', function(event) { + if (event.target.classList.contains('modal')) { + event.target.classList.add('hidden'); + } + }); + } + 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(); }); -} +}); diff --git a/cvat/apps/dashboard/static/dashboard/stylesheet.css b/cvat/apps/dashboard/static/dashboard/stylesheet.css index 4ae41b89e911..bd5bcc8eba0f 100644 --- a/cvat/apps/dashboard/static/dashboard/stylesheet.css +++ b/cvat/apps/dashboard/static/dashboard/stylesheet.css @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ -.dashboardTaskUI { +.dashboardItem { margin: 5px auto; width: 1200px; height: 335px; diff --git a/cvat/apps/dashboard/templates/dashboard/dashboard.html b/cvat/apps/dashboard/templates/dashboard/dashboard.html index 18ddd0882f93..08baf9cf72e8 100644 --- a/cvat/apps/dashboard/templates/dashboard/dashboard.html +++ b/cvat/apps/dashboard/templates/dashboard/dashboard.html @@ -3,6 +3,7 @@ SPDX-License-Identifier: MIT --> + {% extends 'engine/base.html' %} {% load static %} {% load pagination_tags %} @@ -15,6 +16,8 @@ {{ block.super }} + + {% for css_file in css_3rdparty %} {% endfor %} @@ -23,6 +26,7 @@ {% block head_js_3rdparty %} {{ block.super }} + {% for js_file in js_3rdparty %} {% endfor %} @@ -36,10 +40,6 @@ - {% endblock %} {% block content %} @@ -58,13 +58,9 @@
- {% autopaginate data %} -
- {% for item in data %} - {% include "dashboard/task.html" %} - {% endfor %} +
+
-
{% paginate %}
${link}
- {% for segm in item.segment_set.all %} - {% for job in segm.job_set.all %} - - - - {% endfor %} - {% endfor %} -
{{base_url}}?id={{job.id}}
- - diff --git a/cvat/apps/dashboard/tests.py b/cvat/apps/dashboard/tests.py deleted file mode 100644 index ba48a2a3d51c..000000000000 --- a/cvat/apps/dashboard/tests.py +++ /dev/null @@ -1,7 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -# Create your tests here. - diff --git a/cvat/apps/dashboard/urls.py b/cvat/apps/dashboard/urls.py index 3f732f0e3ed5..9c2cc5380e92 100644 --- a/cvat/apps/dashboard/urls.py +++ b/cvat/apps/dashboard/urls.py @@ -9,5 +9,6 @@ urlpatterns = [ path('get_share_nodes', views.JsTreeView), path('', views.DashboardView), + path('meta', views.DashboardMeta), ] diff --git a/cvat/apps/dashboard/views.py b/cvat/apps/dashboard/views.py index 1b2f81dccc84..864369b886f7 100644 --- a/cvat/apps/dashboard/views.py +++ b/cvat/apps/dashboard/views.py @@ -53,29 +53,18 @@ def JsTreeView(request): return JsonResponse(response, safe=False, json_dumps_params=dict(ensure_ascii=False)) - @login_required def DashboardView(request): - query_name = request.GET['search'] if 'search' in request.GET else None - query_job = int(request.GET['jid']) if 'jid' in request.GET and request.GET['jid'].isdigit() else None - task_list = None - - if query_job is not None and JobModel.objects.filter(pk = query_job).exists(): - task_list = [JobModel.objects.select_related('segment__task').get(pk = query_job).segment.task] - else: - task_list = list(TaskModel.objects.prefetch_related('segment_set__job_set').order_by('-created_date').all()) - if query_name is not None: - task_list = list(filter(lambda x: query_name.lower() in x.name.lower(), task_list)) - - task_list = list(filter(lambda task: request.user.has_perm( - 'engine.task.access', task), task_list)) - return render(request, 'dashboard/dashboard.html', { - 'data': task_list, + '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'), - 'js_3rdparty': JS_3RDPARTY.get('dashboard', []), - 'css_3rdparty': CSS_3RDPARTY.get('dashboard', []), - }) + }) \ No newline at end of file diff --git a/cvat/apps/documentation/__init__.py b/cvat/apps/documentation/__init__.py index 6808b8600f35..743392981f00 100644 --- a/cvat/apps/documentation/__init__.py +++ b/cvat/apps/documentation/__init__.py @@ -5,5 +5,4 @@ from cvat.settings.base import JS_3RDPARTY -JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['documentation/js/shortcuts.js'] - +JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['documentation/js/dashboardPlugin.js'] diff --git a/cvat/apps/documentation/static/documentation/js/dashboardPlugin.js b/cvat/apps/documentation/static/documentation/js/dashboardPlugin.js new file mode 100644 index 000000000000..7f948c9c5f28 --- /dev/null +++ b/cvat/apps/documentation/static/documentation/js/dashboardPlugin.js @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2018 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +window.addEventListener('DOMContentLoaded', () => { + $(``).on('click', () => { + window.open('/documentation/user_guide.html'); + }).appendTo('#dashboardManageButtons'); +}); diff --git a/cvat/apps/documentation/static/documentation/js/shortcuts.js b/cvat/apps/documentation/static/documentation/js/shortcuts.js deleted file mode 100644 index 5d6839a3ffeb..000000000000 --- a/cvat/apps/documentation/static/documentation/js/shortcuts.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -/* global - Mousetrap:false -*/ - -Mousetrap.bind(window.cvat.config.shortkeys["open_help"].value, function() { - window.open("/documentation/user_guide.html"); - - return false; -}); diff --git a/cvat/apps/engine/migrations/0015_rest_api_20190217.py b/cvat/apps/engine/migrations/0015_rest_api_20190217.py index f9989c7aa929..1022443aff1e 100644 --- a/cvat/apps/engine/migrations/0015_rest_api_20190217.py +++ b/cvat/apps/engine/migrations/0015_rest_api_20190217.py @@ -125,7 +125,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='task', name='image_quality', - field=models.PositiveSmallIntegerField(), + field=models.PositiveSmallIntegerField(default=50), ), migrations.CreateModel( name='Plugin', diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index a27afd84bff5..25f15bf0d422 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -66,7 +66,7 @@ class Task(models.Model): segment_size = models.PositiveIntegerField(default=0) z_order = models.BooleanField(default=False) flipped = models.BooleanField(default=False) - image_quality = models.PositiveSmallIntegerField() + image_quality = models.PositiveSmallIntegerField(default=50) status = models.CharField(max_length=32, choices=StatusChoice.choices(), default=StatusChoice.ANNOTATION) diff --git a/cvat/apps/engine/static/engine/js/annotationUI.js b/cvat/apps/engine/static/engine/js/annotationUI.js index 746f27cc651f..ac0bc3727728 100644 --- a/cvat/apps/engine/static/engine/js/annotationUI.js +++ b/cvat/apps/engine/static/engine/js/annotationUI.js @@ -57,7 +57,7 @@ function callAnnotationUI(jid) { serverRequest("/api/v1/jobs/" + jid, function(job) { serverRequest("/api/v1/tasks/" + job.task_id, function(task) { serverRequest("/api/v1/tasks/" + job.task_id + "/frames/meta", function(imageMetaCache) { - serverRequest("get/annotation/job/" + jid, function(data) { + serverRequest(`/api/v1/tasks/${jid}/annotation`, function(data) { $('#loadingOverlay').remove(); setTimeout(() => { // FIXME: code cloning diff --git a/cvat/apps/engine/static/engine/js/base.js b/cvat/apps/engine/static/engine/js/base.js index 1674b3589c48..953baaa6937a 100644 --- a/cvat/apps/engine/static/engine/js/base.js +++ b/cvat/apps/engine/static/engine/js/base.js @@ -24,6 +24,35 @@ Math.clamp = function(x, min, max) { return Math.min(Math.max(x, min), max); }; +String.customSplit = function(string, separator) { + const regex = /"/gi; + const occurences = []; + let occurence = null; + while ( (occurence = regex.exec(string)) ) { + occurences.push(occurence.index); + } + + if (occurences.length % 2) { + occurences.pop(); + } + + let copy = ""; + if (occurences.length) { + let start = 0; + for (let idx = 0; idx < occurences.length; idx += 2) { + copy += string.substr(start, occurences[idx] - start); + copy += string.substr(occurences[idx], occurences[idx + 1] - occurences[idx] + 1) + .replace(new RegExp(separator, 'g'), '\0'); + start = occurences[idx + 1] + 1; + } + copy += string.substr(occurences[occurences.length - 1] + 1); + } else { + copy = string; + } + + return copy.split(new RegExp(separator, 'g')).map((x) => x.replace(/\0/g, separator)); +}; + function userConfirm(message, onagree, ondisagree) { let template = $('#confirmTemplate'); diff --git a/cvat/apps/engine/static/engine/js/labelsInfo.js b/cvat/apps/engine/static/engine/js/labelsInfo.js index f7880494f055..e4401d158fb4 100644 --- a/cvat/apps/engine/static/engine/js/labelsInfo.js +++ b/cvat/apps/engine/static/engine/js/labelsInfo.js @@ -14,9 +14,9 @@ class LabelsInfo { constructor(job) { - this._labels = new Object; - this._attributes = new Object; - this._colorIdxs = new Object; + this._labels = {}; + this._attributes = {}; + this._colorIdxs = {}; for (let labelKey in job.labels) { let label = { @@ -50,6 +50,40 @@ class LabelsInfo { } } + restConstructor(labels) { + this._labels = {}; + this._attributes = {}; + this._colorIdxs = {}; + + for (let label of labels) { + this._labels[label.id] = { + name: label.name, + attributes: {}, + } + + for (let attr of label.attributes) { + this._attributes[attr.id] = convertAttribute(attr); + this._labels[label.id].attributes[attr.id] = this._attributes[attr.id]; + + } + + this._colorIdxs[label.id] = +label.id; + } + + function convertAttribute(attribute) { + return { + mutable: attribute.mutable, + type: attribute.input_type, + name: attribute.name, + values: attribute.input_type === 'checkbox' ? + [attribute.values[0].toLoweCase() !== 'false' && attribute.values[0] !== false] : + attribute.values, + } + } + + return this; + } + labelColorIdx(labelId) { return this._colorIdxs[labelId]; } @@ -143,11 +177,60 @@ class LabelsInfo { strToValues(type, string) { switch (type) { case 'checkbox': - return [string !== '0' && string !== 'false' && string !== false]; + return [string !== '0' && string.toLoweCase() !== 'false' && string !== false]; case 'text': return [string]; default: return string.toString().split(','); } } + + static serialize(deserialized) { + let serialized = ""; + for (let label of deserialized) { + serialized += " " + label.name; + for (let attr of label.attributes) { + serialized += ' ' + (attr.mutable? "~":"@"); + serialized += attr.input_type + '=' + attr.name + ':'; + serialized += attr.values.join(','); + } + } + + return serialized.trim(); + } + + static deserialize(serialized) { + const normalized = serialized.replace(/'+/g, `'`).replace(/"+/g, `"`).replace(/\s+/g, ` `).trim(); + const fragments = String.customSplit(normalized, ' '); + + const deserialized = []; + let latest = null; + for (let fragment of fragments) { + fragment = fragment.replace(/\+/g, ' ').trim(); + if ((fragment.startsWith('~')) || (fragment.startsWith('@'))) { + const regex = /(@|~)(checkbox|select|number|text|radio)=([,?!-_0-9a-zA-Z()\s"]+):([,?!-_0-9a-zA-Z()"\s]+)/g; + const result = regex.exec(fragment); + if (result === null || latest === null) { + throw Error('Bad labels format'); + } + + const values = String.customSplit(result[4], ','); + latest.attributes.push({ + name: result[3], + mutable: result[1] === '~', + input_type: result[2], + default_value: values[0], + values: values, + }); + } else { + latest = { + name: fragment, + attributes: [] + }; + + deserialized.push(latest); + } + } + return deserialized; + } } diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py index a6eeed627773..e3dbb33dc153 100644 --- a/cvat/apps/engine/task.py +++ b/cvat/apps/engine/task.py @@ -42,7 +42,7 @@ def create(tid, data): @transaction.atomic def rq_handler(job, exc_type, exc_value, traceback): - tid = job.id.split('/')[1] + tid = job.id.split('/')[-1] db_task = models.Task.objects.select_for_update().get(pk=tid) with open(db_task.get_log_path(), "wt") as log_file: print_exception(exc_type, exc_value, traceback, file=log_file) @@ -190,7 +190,7 @@ def _copy_video_to_task(video, db_task): shutil.copyfile(image_orig_path, image_dest_path) image = Image.open(db_task.get_frame_path(0)) - models.Video.objects.create(db_task, path=video, + models.Video.objects.create(task=db_task, path=video, start_frame=0, stop_frame=db_task.size, step=1, width=image.width, height=image.height) image.close() @@ -303,6 +303,7 @@ def _create_thread(tid, data): if video: db_task.mode = "interpolation" + video = os.path.join(upload_dir, video) _copy_video_to_task(video, db_task) else: db_task.mode = "annotation"