2016-10-26 22:26:03 +00:00
import React from 'react' ;
import '../css/TaskListItem.scss' ;
2016-10-28 19:40:03 +00:00
import Console from '../Console' ;
2016-11-02 14:56:23 +00:00
import statusCodes from '../classes/StatusCodes' ;
2016-11-03 17:17:58 +00:00
import pendingActions from '../classes/PendingActions' ;
import ErrorMessage from './ErrorMessage' ;
2016-10-26 22:26:03 +00:00
class TaskListItem extends React . Component {
2016-11-02 22:32:24 +00:00
constructor ( props ) {
2016-10-26 22:26:03 +00:00
super ( ) ;
2016-10-27 16:26:15 +00:00
this . state = {
2016-11-02 22:32:24 +00:00
expanded : false ,
task : { } ,
2016-11-03 17:17:58 +00:00
time : props . data . processing _time ,
actionError : "" ,
actionButtonsDisabled : false
2016-11-02 22:32:24 +00:00
}
for ( let k in props . data ) {
this . state . task [ k ] = props . data [ k ] ;
2016-10-27 16:26:15 +00:00
}
this . toggleExpanded = this . toggleExpanded . bind ( this ) ;
2016-10-31 21:09:01 +00:00
this . consoleOutputUrl = this . consoleOutputUrl . bind ( this ) ;
2016-10-26 22:26:03 +00:00
}
2016-11-02 22:32:24 +00:00
shouldRefresh ( ) {
// If a task is completed, or failed, etc. we don't expect it to change
2016-11-04 18:19:18 +00:00
return ( ( [ statusCodes . QUEUED , statusCodes . RUNNING , null ] . indexOf ( this . state . task . status ) !== - 1 && this . state . task . processing _node ) ||
( ! this . state . task . uuid && this . state . task . processing _node && ! this . state . task . last _error ) ||
this . state . task . pending _action !== null ) ;
2016-11-02 22:32:24 +00:00
}
loadTimer ( startTime ) {
if ( ! this . processingTimeInterval ) {
this . setState ( { time : startTime } ) ;
this . processingTimeInterval = setInterval ( ( ) => {
this . setState ( { time : this . state . time += 1000 } ) ;
} , 1000 ) ;
}
}
unloadTimer ( ) {
if ( this . processingTimeInterval ) clearInterval ( this . processingTimeInterval ) ;
if ( this . state . task . processing _time ) this . setState ( { time : this . state . task . processing _time } ) ;
}
2016-10-26 22:26:03 +00:00
componentDidMount ( ) {
2016-11-03 17:17:58 +00:00
if ( this . shouldRefresh ( ) ) this . refreshTimeout = setTimeout ( ( ) => this . refresh ( ) , this . props . refreshInterval || 3000 ) ;
2016-11-02 22:32:24 +00:00
// Load timer if we are in running state
if ( this . state . task . status === statusCodes . RUNNING ) this . loadTimer ( this . state . task . processing _time ) ;
}
2016-11-03 17:17:58 +00:00
refresh ( ) {
// Fetch
this . refreshRequest = $ . getJSON ( ` /api/projects/ ${ this . state . task . project } /tasks/ ${ this . state . task . id } / ` , json => {
if ( json . id ) {
let oldStatus = this . state . task . status ;
this . setState ( { task : json , actionButtonsDisabled : false } ) ;
// Update timer if we switched to running
if ( oldStatus !== this . state . task . status ) {
if ( this . state . task . status === statusCodes . RUNNING ) {
2016-11-04 18:19:18 +00:00
this . console . clear ( ) ;
2016-11-03 17:17:58 +00:00
this . loadTimer ( this . state . task . processing _time ) ;
} else {
this . setState ( { time : this . state . task . processing _time } ) ;
this . unloadTimer ( ) ;
}
}
} else {
console . warn ( "Cannot refresh task: " + json ) ;
}
2016-11-04 18:19:18 +00:00
if ( this . shouldRefresh ( ) ) this . refreshTimeout = setTimeout ( ( ) => this . refresh ( ) , this . props . refreshInterval || 3000 ) ;
2016-11-03 17:17:58 +00:00
} )
2016-11-04 18:19:18 +00:00
. fail ( ( _ , _ _ , errorThrown ) => {
if ( errorThrown === "Not Found" ) { // Don't translate this one
// Assume this has been deleted
if ( this . props . onDelete ) this . props . onDelete ( this . state . task . id ) ;
2016-11-03 17:17:58 +00:00
}
} ) ;
}
2016-11-02 22:32:24 +00:00
componentWillUnmount ( ) {
this . unloadTimer ( ) ;
if ( this . refreshRequest ) this . refreshRequest . abort ( ) ;
2016-11-04 18:19:18 +00:00
if ( this . refreshTimeout ) clearTimeout ( this . refreshTimeout ) ;
2016-10-26 22:26:03 +00:00
}
2016-10-27 16:26:15 +00:00
toggleExpanded ( ) {
this . setState ( {
expanded : ! this . state . expanded
} ) ;
}
2016-10-31 21:09:01 +00:00
consoleOutputUrl ( line ) {
2016-11-02 22:32:24 +00:00
return ` /api/projects/ ${ this . state . task . project } /tasks/ ${ this . state . task . id } /output/?line= ${ line } ` ;
}
hoursMinutesSecs ( t ) {
if ( t === 0 || t === - 1 ) return "-- : -- : --" ;
let ch = 60 * 60 * 1000 ,
cm = 60 * 1000 ,
h = Math . floor ( t / ch ) ,
m = Math . floor ( ( t - h * ch ) / cm ) ,
s = Math . round ( ( t - h * ch - m * cm ) / 1000 ) ,
pad = function ( n ) { return n < 10 ? '0' + n : n ; } ;
if ( s === 60 ) {
m ++ ;
s = 0 ;
}
if ( m === 60 ) {
h ++ ;
m = 0 ;
}
return [ pad ( h ) , pad ( m ) , pad ( s ) ] . join ( ':' ) ;
2016-10-31 21:09:01 +00:00
}
2016-11-04 18:19:18 +00:00
genActionApiCall ( action , options = { } ) {
2016-11-03 22:13:26 +00:00
return ( ) => {
2016-11-04 18:19:18 +00:00
const doAction = ( ) => {
this . setState ( { actionButtonsDisabled : true } ) ;
2016-11-03 22:13:26 +00:00
2016-11-07 22:25:33 +00:00
let url = ` /api/projects/ ${ this . state . task . project } /tasks/ ${ this . state . task . id } / ${ action } / ` ;
$ . post ( url ,
2016-11-04 18:19:18 +00:00
{
uuid : this . state . task . uuid
}
) . done ( json => {
if ( json . success ) {
this . refresh ( ) ;
if ( options . success !== undefined ) options . success ( ) ;
} else {
this . setState ( {
actionError : json . error ,
actionButtonsDisabled : false
} ) ;
}
} )
. fail ( ( ) => {
2016-11-03 22:13:26 +00:00
this . setState ( {
2016-11-04 18:19:18 +00:00
error : url + " is unreachable." ,
2016-11-03 22:13:26 +00:00
actionButtonsDisabled : false
} ) ;
2016-11-04 18:19:18 +00:00
} ) ;
}
if ( options . confirm ) {
if ( window . confirm ( options . confirm ) ) {
doAction ( ) ;
}
} else {
doAction ( ) ;
}
2016-11-03 22:13:26 +00:00
} ;
}
2016-11-04 18:19:18 +00:00
optionsToList ( options ) {
if ( ! Array . isArray ( options ) ) return "" ;
else if ( options . length === 0 ) return "Default" ;
else {
return options . map ( opt => ` ${ opt . name } : ${ opt . value } ` ) . join ( ", " ) ;
}
}
2016-10-26 22:26:03 +00:00
render ( ) {
2016-11-02 22:32:24 +00:00
let name = this . state . task . name !== null ? this . state . task . name : ` Task # ${ this . state . task . id } ` ;
2016-10-27 16:26:15 +00:00
2016-11-03 17:17:58 +00:00
let status = statusCodes . description ( this . state . task . status ) ;
if ( status === "" ) status = "Uploading images" ;
2016-11-04 18:19:18 +00:00
if ( ! this . state . task . processing _node ) status = "" ;
2016-11-03 17:17:58 +00:00
if ( this . state . task . pending _action !== null ) status = pendingActions . description ( this . state . task . pending _action ) ;
2016-10-27 16:26:15 +00:00
let expanded = "" ;
if ( this . state . expanded ) {
2016-11-03 17:17:58 +00:00
let actionButtons = [ ] ;
const addActionButton = ( label , className , icon , onClick ) => {
actionButtons . push ( {
className , icon , label , onClick
} ) ;
} ;
2016-11-04 18:19:18 +00:00
if ( [ statusCodes . QUEUED , statusCodes . RUNNING , null ] . indexOf ( this . state . task . status ) !== - 1 &&
this . state . task . processing _node ) {
2016-11-03 22:13:26 +00:00
addActionButton ( "Cancel" , "btn-primary" , "glyphicon glyphicon-remove-circle" , this . genActionApiCall ( "cancel" ) ) ;
2016-11-03 17:17:58 +00:00
}
2016-11-04 18:19:18 +00:00
if ( [ statusCodes . FAILED , statusCodes . COMPLETED , statusCodes . CANCELED ] . indexOf ( this . state . task . status ) !== - 1 &&
this . state . task . processing _node ) {
addActionButton ( "Restart" , "btn-primary" , "glyphicon glyphicon-remove-circle" , this . genActionApiCall ( "restart" , {
success : ( ) => {
this . console . clear ( ) ;
this . setState ( { time : - 1 } ) ;
}
}
) ) ;
}
2016-11-03 17:17:58 +00:00
2016-11-04 18:19:18 +00:00
// TODO: ability to change options
2016-11-03 17:17:58 +00:00
// addActionButton("Edit", "btn-primary", "glyphicon glyphicon-pencil", () => {
// console.log("edit call");
// });
2016-11-04 18:19:18 +00:00
addActionButton ( "Delete" , "btn-danger" , "glyphicon glyphicon-trash" , this . genActionApiCall ( "remove" , {
confirm : "All information related to this task, including images, maps and models will be deleted. Continue?"
} ) ) ;
2016-11-03 17:17:58 +00:00
actionButtons = ( < div className = "action-buttons" >
{ actionButtons . map ( button => {
return (
< button key = { button . label } type = "button" className = { "btn btn-sm " + button . className } onClick = { button . onClick } disabled = { this . state . actionButtonsDisabled || ! ! this . state . task . pending _action } >
< i className = { button . icon } > < / i >
{ button . label }
< / button >
)
} ) }
2016-11-02 22:32:24 +00:00
< / div > ) ;
2016-10-27 16:26:15 +00:00
expanded = (
2016-10-31 21:09:01 +00:00
< div className = "expanded-panel" >
< div className = "row" >
< div className = "col-md-4 no-padding" >
< div className = "labels" >
2016-11-03 17:17:58 +00:00
< strong > Created on : < / strong > { ( new Date ( this . state . task . created _at ) ) . toLocaleString ( ) } < br / >
2016-11-02 22:32:24 +00:00
< / div >
< div className = "labels" >
2016-11-03 17:17:58 +00:00
< strong > Status : < / strong > { status } < br / >
2016-11-04 18:19:18 +00:00
< / div >
< div className = "labels" >
< strong > Options : < / strong > { this . optionsToList ( this . state . task . options ) } < br / >
2016-10-31 21:09:01 +00:00
< / div >
2016-11-02 22:32:24 +00:00
{ /* TODO: List of images? */ }
2016-10-27 16:26:15 +00:00
< / div >
2016-10-31 21:09:01 +00:00
< div className = "col-md-8" >
< Console
source = { this . consoleOutputUrl }
2016-11-02 22:32:24 +00:00
refreshInterval = { this . shouldRefresh ( ) ? 3000 : undefined }
2016-10-31 21:09:01 +00:00
autoscroll = { true }
2016-11-04 18:19:18 +00:00
height = { 200 }
ref = { domNode => this . console = domNode }
/ >
2016-10-31 21:09:01 +00:00
< / div >
< / div >
< div className = "row" >
2016-11-03 17:17:58 +00:00
< ErrorMessage message = { this . state . actionError } / >
2016-11-02 22:32:24 +00:00
{ actionButtons }
2016-10-27 16:26:15 +00:00
< / div >
2016-10-31 21:09:01 +00:00
< / div >
) ;
2016-10-27 16:26:15 +00:00
}
2016-11-02 22:32:24 +00:00
const getStatusLabel = ( text , classes = "" ) => {
return ( < div className = { "status-label " + classes } title = { text } > { text } < / div > ) ;
}
2016-11-02 14:56:23 +00:00
let statusLabel = "" ;
2016-11-02 22:32:24 +00:00
let statusIcon = statusCodes . icon ( this . state . task . status ) ;
if ( this . state . task . last _error ) {
statusLabel = getStatusLabel ( this . state . task . last _error , "error" ) ;
} else if ( ! this . state . task . processing _node ) {
statusLabel = getStatusLabel ( "Processing node not set" ) ;
statusIcon = "fa fa-hourglass-3" ;
2016-11-02 14:56:23 +00:00
} else {
2016-11-03 17:17:58 +00:00
statusLabel = getStatusLabel ( status , this . state . task . status == 40 ? "done" : "" ) ;
2016-11-02 14:56:23 +00:00
}
2016-10-26 22:26:03 +00:00
return (
< div className = "task-list-item" >
2016-10-27 16:26:15 +00:00
< div className = "row" >
< div className = "col-md-5 name" >
< i onClick = { this . toggleExpanded } className = { "clickable fa " + ( this . state . expanded ? "fa-minus-square-o" : " fa-plus-square-o" ) } > < / i > < a href = "javascript:void(0);" onClick = { this . toggleExpanded } > { name } < / a >
< / div >
2016-11-02 22:32:24 +00:00
< div className = "col-md-1 details" >
< i className = "fa fa-image" > < / i > { this . state . task . images _count }
2016-10-27 16:26:15 +00:00
< / div >
2016-11-02 22:32:24 +00:00
< div className = "col-md-2 details" >
< i className = "fa fa-clock-o" > < / i > { this . hoursMinutesSecs ( this . state . time ) }
< / div >
2016-11-02 14:56:23 +00:00
< div className = "col-md-3" >
{ statusLabel }
< / div >
< div className = "col-md-1 text-right" >
2016-11-02 22:32:24 +00:00
< div className = "status-icon" >
< i className = { statusIcon } > < / i >
< / div >
2016-10-27 16:26:15 +00:00
< / div >
< / div >
{ expanded }
2016-10-26 22:26:03 +00:00
< / div >
) ;
}
}
2016-10-27 16:26:15 +00:00
export default TaskListItem ;