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' ;
2017-07-18 20:07:53 +00:00
import EditTaskPanel from './EditTaskPanel' ;
2016-11-11 16:00:31 +00:00
import AssetDownloadButtons from './AssetDownloadButtons' ;
2017-04-13 21:03:42 +00:00
import HistoryNav from '../classes/HistoryNav' ;
2017-09-06 15:47:04 +00:00
import PropTypes from 'prop-types' ;
2018-07-26 15:05:00 +00:00
import TaskPluginActionButtons from './TaskPluginActionButtons' ;
2018-12-06 20:44:53 +00:00
import PipelineSteps from '../classes/PipelineSteps' ;
2018-12-07 20:13:49 +00:00
import Css from '../classes/Css' ;
2016-10-26 22:26:03 +00:00
class TaskListItem extends React . Component {
2017-09-06 15:47:04 +00:00
static propTypes = {
history : PropTypes . object . isRequired ,
data : PropTypes . object . isRequired , // task json
refreshInterval : PropTypes . number , // how often to refresh info
onDelete : PropTypes . func
}
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
2017-04-13 21:03:42 +00:00
this . historyNav = new HistoryNav ( props . history ) ;
2016-10-27 16:26:15 +00:00
this . state = {
2017-04-13 21:03:42 +00:00
expanded : this . historyNav . isValueInQSList ( "project_task_expanded" , props . data . id ) ,
2016-11-02 22:32:24 +00:00
task : { } ,
2016-11-03 17:17:58 +00:00
time : props . data . processing _time ,
actionError : "" ,
2017-02-09 18:06:44 +00:00
actionButtonsDisabled : false ,
2017-03-27 19:14:46 +00:00
editing : false ,
2018-01-11 19:41:06 +00:00
memoryError : false ,
2018-09-04 15:19:22 +00:00
friendlyTaskError : "" ,
2018-12-06 00:18:54 +00:00
pluginActionButtons : [ ] ,
view : "basic"
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 ) ;
2017-02-09 18:06:44 +00:00
this . stopEditing = this . stopEditing . bind ( this ) ;
2017-02-10 17:28:32 +00:00
this . startEditing = this . startEditing . bind ( this ) ;
2018-01-11 19:41:06 +00:00
this . checkForCommonErrors = this . checkForCommonErrors . bind ( this ) ;
2017-07-18 20:07:53 +00:00
this . handleEditTaskSave = this . handleEditTaskSave . bind ( this ) ;
2018-12-06 00:18:54 +00:00
this . setView = this . setView . bind ( this ) ;
2018-12-07 20:13:49 +00:00
// Retrieve CSS values for status bar colors
this . backgroundSuccessColor = Css . getValue ( 'theme-background-success' , 'backgroundColor' ) ;
this . backgroundFailedColor = Css . getValue ( 'theme-background-failed' , 'backgroundColor' ) ;
2016-10-26 22:26:03 +00:00
}
2016-11-02 22:32:24 +00:00
shouldRefresh ( ) {
2017-07-25 17:54:41 +00:00
if ( this . state . task . pending _action !== null ) return true ;
2016-11-02 22:32:24 +00:00
// If a task is completed, or failed, etc. we don't expect it to change
2017-07-24 20:59:57 +00:00
if ( [ statusCodes . COMPLETED , statusCodes . FAILED , statusCodes . CANCELED ] . indexOf ( this . state . task . status ) !== - 1 ) return false ;
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 ) ||
2017-07-25 17:54:41 +00:00
( ! this . state . task . uuid && this . state . task . processing _node && ! this . state . task . last _error ) ) ;
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 ) ;
}
}
2018-12-06 00:18:54 +00:00
setView ( type ) {
return ( ) => {
this . setState ( { view : type } ) ;
}
}
2016-11-02 22:32:24 +00:00
unloadTimer ( ) {
2018-11-03 19:13:00 +00:00
if ( this . processingTimeInterval ) {
clearInterval ( this . processingTimeInterval ) ;
this . processingTimeInterval = null ;
}
2016-11-02 22:32:24 +00:00
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-09 13:52:50 +00:00
if ( this . console ) this . console . clear ( ) ;
2018-12-06 00:18:54 +00:00
if ( this . basicView ) this . basicView . reset ( ) ;
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 ( ) ;
}
2017-03-27 19:14:46 +00:00
if ( this . state . task . status !== statusCodes . FAILED ) {
2018-09-04 15:19:22 +00:00
this . setState ( { memoryError : false , friendlyTaskError : "" } ) ;
2017-03-27 19:14:46 +00:00
}
2016-11-03 17:17:58 +00:00
}
} else {
console . warn ( "Cannot refresh task: " + json ) ;
}
2018-10-18 12:44:07 +00:00
2017-02-25 18:25:47 +00:00
this . setAutoRefresh ( ) ;
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 ) ;
2017-02-18 17:58:38 +00:00
} else {
2017-02-25 18:25:47 +00:00
this . setAutoRefresh ( ) ;
2016-11-03 17:17:58 +00:00
}
} ) ;
}
2017-02-25 18:25:47 +00:00
setAutoRefresh ( ) {
if ( this . shouldRefresh ( ) ) this . refreshTimeout = setTimeout ( ( ) => this . refresh ( ) , this . props . refreshInterval || 3000 ) ;
}
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 ( ) {
2017-04-13 21:03:42 +00:00
const expanded = ! this . state . expanded ;
this . historyNav . toggleQSListItem ( "project_task_expanded" , this . props . data . id , expanded ) ;
2018-10-18 12:44:07 +00:00
2016-10-27 16:26:15 +00:00
this . setState ( {
2017-04-13 21:03:42 +00:00
expanded : expanded
2016-10-27 16:26:15 +00:00
} ) ;
}
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 ( {
2017-06-15 17:16:00 +00:00
actionError : json . error || options . defaultError || "Cannot complete operation." ,
2016-11-04 18:19:18 +00:00
actionButtonsDisabled : false
} ) ;
}
} )
. fail ( ( ) => {
2016-11-03 22:13:26 +00:00
this . setState ( {
2017-06-15 17:16:00 +00:00
actionError : options . defaultError || "Cannot complete operation." ,
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 ( ", " ) ;
}
}
2017-02-10 17:28:32 +00:00
startEditing ( ) {
2017-07-18 20:07:53 +00:00
this . setState ( { expanded : true , editing : true } ) ;
2017-02-10 17:28:32 +00:00
}
2017-02-09 18:06:44 +00:00
stopEditing ( ) {
this . setState ( { editing : false } ) ;
}
2018-01-11 19:41:06 +00:00
checkForCommonErrors ( lines ) {
2017-03-27 19:14:46 +00:00
for ( let line of lines ) {
2018-10-18 12:44:07 +00:00
if ( line . indexOf ( "Killed" ) !== - 1 ||
line . indexOf ( "MemoryError" ) !== - 1 ||
2017-11-29 22:29:50 +00:00
line . indexOf ( "std::bad_alloc" ) !== - 1 ||
2018-04-15 22:29:53 +00:00
line . indexOf ( "Child returned 137" ) !== - 1 ||
2018-09-27 02:40:34 +00:00
line . indexOf ( "loky.process_executor.TerminatedWorkerError:" ) !== - 1 ||
2018-04-15 22:29:53 +00:00
line . indexOf ( "Failed to allocate memory" ) !== - 1 ) {
2017-03-27 19:14:46 +00:00
this . setState ( { memoryError : true } ) ;
2018-11-03 18:50:31 +00:00
} else if ( line . indexOf ( "SVD did not converge" ) !== - 1 ||
line . indexOf ( "0 partial reconstructions in total" ) !== - 1 ) {
2019-06-25 18:54:29 +00:00
this . setState ( { friendlyTaskError : ` It looks like there might be one of the following problems:
2018-09-04 15:19:22 +00:00
< ul >
< li > Not enough images < / li >
< li > Not enough overlap between images < / li >
< li > Images might be too blurry ( common with phone cameras ) < / li >
2019-06-25 18:54:29 +00:00
< li > The min - num - features task option is set too low , try increasing it by 25 % < / li >
2018-09-04 15:19:22 +00:00
< / ul >
You can read more about best practices for capturing good images < a href = 'https://support.dronedeploy.com/v1.0/docs/making-successful-maps' target = '_blank' > here < / a > . ` });
2018-09-03 11:38:09 +00:00
} else if ( line . indexOf ( "Illegal instruction" ) !== - 1 ||
line . indexOf ( "Child returned 132" ) !== - 1 ) {
2018-09-04 15:19:22 +00:00
this . setState ( { friendlyTaskError : "It looks like this computer might be too old. WebODM requires a computer with a 64-bit CPU supporting MMX, SSE, SSE2, SSE3 and SSSE3 instruction set support or higher. You can still run WebODM if you compile your own docker images. See <a href='https://github.com/OpenDroneMap/WebODM#common-troubleshooting'>this page</a> for more information." } ) ;
} else if ( line . indexOf ( "Child returned 127" ) !== - 1 ) {
this . setState ( { friendlyTaskError : "The processing node is missing a program necessary to complete the task. This might indicate a corrupted installation. If you built OpenDroneMap, please check that all programs built without errors." } ) ;
2017-03-27 19:14:46 +00:00
}
}
}
2017-05-02 00:07:48 +00:00
isMacOS ( ) {
return window . navigator . platform === "MacIntel" ;
}
2017-07-18 20:07:53 +00:00
handleEditTaskSave ( task ) {
this . setState ( { task , editing : false } ) ;
this . setAutoRefresh ( ) ;
}
2018-01-25 01:09:59 +00:00
getRestartSubmenuItems ( ) {
const { task } = this . state ;
2018-01-24 22:04:53 +00:00
// Map rerun-from parameters to display items
2018-12-06 00:18:54 +00:00
// (remove the first item so that 'dataset' is not displayed)
const rfMap = { } ;
2018-12-06 20:44:53 +00:00
PipelineSteps . get ( ) . slice ( 1 ) . forEach ( rf => rfMap [ rf . action ] = rf ) ;
2018-01-24 22:04:53 +00:00
2018-01-25 01:09:59 +00:00
// Create onClick handlers
for ( let rfParam in rfMap ) {
2018-12-06 00:18:54 +00:00
rfMap [ rfParam ] . label = "From " + rfMap [ rfParam ] . label ;
2018-01-25 01:09:59 +00:00
rfMap [ rfParam ] . onClick = this . genRestartAction ( rfParam ) ;
}
2018-01-24 22:04:53 +00:00
return task . can _rerun _from
. map ( rf => rfMap [ rf ] )
. filter ( rf => rf !== undefined ) ;
}
2018-01-25 01:09:59 +00:00
genRestartAction ( rerunFrom = null ) {
const { task } = this . state ;
const restartAction = this . genActionApiCall ( "restart" , {
success : ( ) => {
this . setState ( { time : - 1 } ) ;
} ,
defaultError : "Cannot restart task."
}
) ;
const setTaskRerunFrom = ( value ) => {
this . setState ( { actionButtonsDisabled : true } ) ;
// Removing rerun-from?
if ( value === null ) {
task . options = task . options . filter ( opt => opt [ 'name' ] !== 'rerun-from' ) ;
} else {
// Adding rerun-from
let opt = null ;
if ( opt = task . options . find ( opt => opt [ 'name' ] === 'rerun-from' ) ) {
opt [ 'value' ] = value ;
} else {
// Not in existing list of options, append
task . options . push ( {
name : 'rerun-from' ,
value : value
} ) ;
}
}
2018-10-18 12:44:07 +00:00
2018-01-25 18:44:46 +00:00
let data = {
options : task . options
} ;
// Force reprocess
if ( value === null ) data . uuid = '' ;
2018-01-25 01:09:59 +00:00
return $ . ajax ( {
url : ` /api/projects/ ${ task . project } /tasks/ ${ task . id } / ` ,
contentType : 'application/json' ,
2018-01-25 18:44:46 +00:00
data : JSON . stringify ( data ) ,
2018-01-25 01:09:59 +00:00
dataType : 'json' ,
type : 'PATCH'
} ) . done ( ( taskJson ) => {
this . setState ( { task : taskJson } ) ;
} )
. fail ( ( ) => {
this . setState ( {
actionError : ` Cannot restart task from ${ value || "the start" } . ` ,
actionButtonsDisabled : false
} ) ;
} ) ;
} ;
return ( ) => {
setTaskRerunFrom ( rerunFrom )
. then ( restartAction ) ;
} ;
}
2016-10-26 22:26:03 +00:00
render ( ) {
2016-11-09 21:13:43 +00:00
const task = this . state . task ;
const name = task . name !== null ? task . name : ` Task # ${ task . id } ` ;
2019-02-20 21:42:20 +00:00
const imported = task . import _url !== "" ;
2017-07-18 20:07:53 +00:00
2016-11-09 21:13:43 +00:00
let status = statusCodes . description ( task . status ) ;
2018-12-07 20:13:49 +00:00
if ( status === "" ) status = "Uploading images to processing node" ;
2017-05-02 00:07:48 +00:00
2019-02-20 21:42:20 +00:00
if ( ! task . processing _node && ! imported ) status = "Waiting for a node..." ;
2016-11-09 21:13:43 +00:00
if ( task . pending _action !== null ) status = pendingActions . description ( task . pending _action ) ;
2016-11-03 17:17:58 +00:00
2019-02-20 21:42:20 +00:00
2016-10-27 16:26:15 +00:00
let expanded = "" ;
if ( this . state . expanded ) {
2017-07-07 19:34:02 +00:00
let showOrthophotoMissingWarning = false ,
2017-05-02 00:07:48 +00:00
showMemoryErrorWarning = this . state . memoryError && task . status == statusCodes . FAILED ,
2018-09-04 15:19:22 +00:00
showTaskWarning = this . state . friendlyTaskError !== "" && task . status == statusCodes . FAILED ,
2018-10-18 12:44:07 +00:00
showExitedWithCodeOneHints = task . last _error === "Process exited with code 1" &&
! showMemoryErrorWarning &&
2018-09-04 15:19:22 +00:00
! showTaskWarning &&
2018-09-03 11:38:09 +00:00
task . status == statusCodes . FAILED ,
2017-05-02 00:07:48 +00:00
memoryErrorLink = this . isMacOS ( ) ? "http://stackoverflow.com/a/39720010" : "https://docs.docker.com/docker-for-windows/#advanced" ;
2018-10-18 12:44:07 +00:00
2016-11-03 17:17:58 +00:00
let actionButtons = [ ] ;
2018-01-24 16:07:06 +00:00
const addActionButton = ( label , className , icon , onClick , options = { } ) => {
2016-11-03 17:17:58 +00:00
actionButtons . push ( {
2018-01-24 16:07:06 +00:00
className , icon , label , onClick , options
2016-11-03 17:17:58 +00:00
} ) ;
} ;
2018-10-18 12:44:07 +00:00
2016-11-09 21:13:43 +00:00
if ( task . status === statusCodes . COMPLETED ) {
2017-07-07 19:34:02 +00:00
if ( task . available _assets . indexOf ( "orthophoto.tif" ) !== - 1 ) {
2017-07-12 17:35:28 +00:00
addActionButton ( " View Map" , "btn-primary" , "fa fa-globe" , ( ) => {
2017-03-14 19:01:18 +00:00
location . href = ` /map/project/ ${ task . project } /task/ ${ task . id } / ` ;
} ) ;
} else {
2017-07-07 19:34:02 +00:00
showOrthophotoMissingWarning = true ;
2017-03-14 19:01:18 +00:00
}
2018-10-18 12:44:07 +00:00
2017-03-15 14:58:06 +00:00
addActionButton ( " View 3D Model" , "btn-primary" , "fa fa-cube" , ( ) => {
location . href = ` /3d/project/ ${ task . project } /task/ ${ task . id } / ` ;
} ) ;
2016-11-09 21:13:43 +00:00
}
2017-02-10 17:28:32 +00:00
// Ability to change options
if ( [ statusCodes . FAILED , statusCodes . COMPLETED , statusCodes . CANCELED ] . indexOf ( task . status ) !== - 1 ||
( ! task . processing _node ) ) {
2017-07-18 21:12:32 +00:00
addActionButton ( "Edit" , "btn-primary pull-right edit-button" , "glyphicon glyphicon-pencil" , ( ) => {
2017-02-10 17:28:32 +00:00
this . startEditing ( ) ;
2018-01-24 16:07:06 +00:00
} , {
className : "inline"
2017-02-10 17:28:32 +00:00
} ) ;
}
2016-11-09 21:13:43 +00:00
if ( [ statusCodes . QUEUED , statusCodes . RUNNING , null ] . indexOf ( task . status ) !== - 1 &&
2019-02-21 19:16:48 +00:00
( task . processing _node || imported ) ) {
2017-06-15 17:16:00 +00:00
addActionButton ( "Cancel" , "btn-primary" , "glyphicon glyphicon-remove-circle" , this . genActionApiCall ( "cancel" , { defaultError : "Cannot cancel task." } ) ) ;
2016-11-03 17:17:58 +00:00
}
2016-11-09 21:13:43 +00:00
if ( [ statusCodes . FAILED , statusCodes . COMPLETED , statusCodes . CANCELED ] . indexOf ( task . status ) !== - 1 &&
2019-02-21 19:16:48 +00:00
task . processing _node &&
! imported ) {
2018-10-18 12:44:07 +00:00
// By default restart reruns every pipeline
2018-01-25 18:44:46 +00:00
// step from the beginning
2018-10-18 12:44:07 +00:00
const rerunFrom = task . can _rerun _from . length > 1 ?
task . can _rerun _from [ 1 ] :
2018-01-25 18:44:46 +00:00
null ;
2018-01-24 22:04:53 +00:00
2018-01-25 18:44:46 +00:00
addActionButton ( "Restart" , "btn-primary" , "glyphicon glyphicon-repeat" , this . genRestartAction ( rerunFrom ) , {
2018-01-25 01:09:59 +00:00
subItems : this . getRestartSubmenuItems ( )
2018-01-24 16:07:06 +00:00
} ) ;
2016-11-04 18:19:18 +00:00
}
2016-11-03 17:17:58 +00:00
2016-11-04 18:19:18 +00:00
addActionButton ( "Delete" , "btn-danger" , "glyphicon glyphicon-trash" , this . genActionApiCall ( "remove" , {
2017-06-15 17:16:00 +00:00
confirm : "All information related to this task, including images, maps and models will be deleted. Continue?" ,
defaultError : "Cannot delete task."
2016-11-04 18:19:18 +00:00
} ) ) ;
2016-11-03 17:17:58 +00:00
2019-01-16 18:04:32 +00:00
const disabled = this . state . actionButtonsDisabled ||
( [ pendingActions . CANCEL ,
pendingActions . REMOVE ,
pendingActions . RESTART ] . indexOf ( task . pending _action ) !== - 1 ) ;
2016-11-11 16:00:31 +00:00
2016-11-03 17:17:58 +00:00
actionButtons = ( < div className = "action-buttons" >
2018-10-18 12:44:07 +00:00
{ task . status === statusCodes . COMPLETED ?
2016-11-14 21:32:05 +00:00
< AssetDownloadButtons task = { this . state . task } disabled = { disabled } / >
: "" }
2016-11-03 17:17:58 +00:00
{ actionButtons . map ( button => {
2018-01-24 16:07:06 +00:00
const subItems = button . options . subItems || [ ] ;
const className = button . options . className || "" ;
2016-11-03 17:17:58 +00:00
return (
2018-10-18 12:44:07 +00:00
< div key = { button . label } className = { "inline-block " +
2018-01-24 16:07:06 +00:00
( subItems . length > 0 ? "btn-group" : "" ) + " " +
className } >
2018-01-25 01:09:59 +00:00
< button type = "button" className = { "btn btn-sm " + button . className } onClick = { button . onClick } disabled = { disabled } >
2018-01-24 16:07:06 +00:00
< i className = { button . icon } > < / i >
{ button . label }
< / button >
2018-10-18 12:44:07 +00:00
{ subItems . length > 0 &&
2018-01-25 01:09:59 +00:00
[ < button key = "dropdown-button"
disabled = { disabled }
2018-10-18 12:44:07 +00:00
type = "button"
className = { "btn btn-sm dropdown-toggle " + button . className }
2018-01-25 01:09:59 +00:00
data - toggle = "dropdown" > < span className = "caret" > < / span > < / button > ,
2018-01-24 16:07:06 +00:00
< ul key = "dropdown-menu" className = "dropdown-menu" >
{ subItems . map ( subItem => < li key = { subItem . label } >
2018-12-06 00:18:54 +00:00
< a href = "javascript:void(0);" onClick = { subItem . onClick } > < i className = { subItem . icon + ' fa-fw ' } > < / i > { subItem . label } < / a >
2018-01-24 16:07:06 +00:00
< / li > ) }
< / ul > ] }
< / div > ) ;
2016-11-03 17:17:58 +00:00
} ) }
2016-11-02 22:32:24 +00:00
< / div > ) ;
2018-12-04 18:04:22 +00:00
2016-10-27 16:26:15 +00:00
expanded = (
2016-10-31 21:09:01 +00:00
< div className = "expanded-panel" >
< div className = "row" >
2019-05-20 14:05:56 +00:00
< div className = "col-md-12 no-padding" >
< div className = "console-switch text-right pull-right" >
< div className = "console-output-label" > Task Output : < / div > < ul className = "list-inline" >
< li >
< div className = "btn-group btn-toggle" >
< button onClick = { this . setView ( "console" ) } className = { "btn btn-xs " + ( this . state . view === "basic" ? "btn-default" : "btn-primary" ) } > On < / button >
< button onClick = { this . setView ( "basic" ) } className = { "btn btn-xs " + ( this . state . view === "console" ? "btn-default" : "btn-primary" ) } > Off < / button >
< / div >
< / li >
< / ul >
2016-11-02 22:32:24 +00:00
< / div >
2017-03-14 19:01:18 +00:00
2019-05-20 14:05:56 +00:00
< div className = "mb" >
< div className = "labels" >
< strong > Created on : < / strong > { ( new Date ( task . created _at ) ) . toLocaleString ( ) } < br / >
< / div >
< div className = "labels" >
< strong > Processing Node : < / strong > { task . processing _node _name || "-" } ( { task . auto _processing _node ? "auto" : "manual" } ) < br / >
< / div >
{ Array . isArray ( task . options ) ?
< div className = "labels" >
< strong > Options : < / strong > { this . optionsToList ( task . options ) } < br / >
< / div >
: "" }
{ /* TODO: List of images? */ }
< / div >
2018-12-06 00:18:54 +00:00
{ this . state . view === 'console' ?
< Console
2018-12-06 21:00:42 +00:00
className = "floatfix"
2018-12-06 00:18:54 +00:00
source = { this . consoleOutputUrl }
refreshInterval = { this . shouldRefresh ( ) ? 3000 : undefined }
autoscroll = { true }
height = { 200 }
ref = { domNode => this . console = domNode }
onAddLines = { this . checkForCommonErrors }
showConsoleButtons = { true }
maximumLines = { 500 }
/ > : " " }
2019-05-20 14:05:56 +00:00
{ showOrthophotoMissingWarning ?
< div className = "task-warning" > < i className = "fa fa-warning" > < / i > < span > An orthophoto could not be generated . To generate one , make sure GPS information is embedded in the EXIF tags of your images , or use a Ground Control Points ( GCP ) file . < / span > < / div > : "" }
2018-11-03 18:46:01 +00:00
2018-10-18 12:44:07 +00:00
{ showMemoryErrorWarning ?
2018-12-10 15:01:38 +00:00
< div className = "task-warning" > < i className = "fa fa-support" > < / i > < span > It looks like your processing node ran out of memory . If you are using docker , make sure that your docker environment has < a href = { memoryErrorLink } target = "_blank" > enough RAM allocated < / a > . Alternatively , make sure you have enough physical RAM , reduce the number of images , make your images smaller , or reduce the max - concurrency parameter from the task ' s < a href = "javascript:void(0);" onClick = { this . startEditing } > options < / a > . You can also try to use a < a href = "https://www.opendronemap.org/webodm/lightning/" target = "_blank" > cloud processing node < / a > . < / span > < / div > : "" }
2018-10-18 12:44:07 +00:00
{ showTaskWarning ?
2018-09-04 15:24:12 +00:00
< div className = "task-warning" > < i className = "fa fa-support" > < / i > < span dangerouslySetInnerHTML = { { _ _html : this . state . friendlyTaskError } } / > < / div > : "" }
2018-01-11 19:41:06 +00:00
2017-05-02 00:07:48 +00:00
{ showExitedWithCodeOneHints ?
< div className = "task-warning" > < i className = "fa fa-info-circle" > < / i > < div className = "inline" >
2019-06-25 18:54:29 +00:00
"Process exited with code 1" means that part of the processing failed . Sometimes it ' s a problem with the dataset , sometimes it can be solved by tweaking the < a href = "javascript:void(0);" onClick = { this . startEditing } > Task Options < / a > and sometimes it might be a bug !
If you need help , upload your images somewhere like < a href = "https://www.dropbox.com/" target = "_blank" > Dropbox < / a > or < a href = "https://drive.google.com/drive/u/0/" target = "_blank" > Google Drive < / a > and < a href = "http://community.opendronemap.org/c/webodm" target = "_blank" > open a topic < / a > on our community forum , making
2018-12-06 00:18:54 +00:00
sure to include a < a href = "javascript:void(0);" onClick = { this . setView ( "console" ) } > copy of your task ' s output < / a > . Our awesome contributors will try to help you ! < i className = "fa fa-smile-o" > < / i >
2017-05-02 00:07:48 +00:00
< / div >
< / div >
: "" }
2016-10-31 21:09:01 +00:00
< / div >
< / div >
2018-11-03 18:50:31 +00:00
< div className = "row clearfix" >
2016-11-14 15:58:00 +00:00
< ErrorMessage bind = { [ this , 'actionError' ] } / >
2016-11-02 22:32:24 +00:00
{ actionButtons }
2016-10-27 16:26:15 +00:00
< / div >
2018-07-26 19:14:48 +00:00
< TaskPluginActionButtons task = { task } disabled = { disabled } / >
2016-10-31 21:09:01 +00:00
< / div >
) ;
2017-07-18 20:07:53 +00:00
// If we're editing, the expanded view becomes the edit panel
if ( this . state . editing ) {
expanded = < div className = "task-list-item" >
< div className = "row no-padding" >
< EditTaskPanel
task = { this . state . task }
onSave = { this . handleEditTaskSave }
onCancel = { this . stopEditing }
/ >
< / div >
< / div > ;
}
2016-10-27 16:26:15 +00:00
}
2018-12-07 20:13:49 +00:00
// @param type {String} one of: ['neutral', 'done', 'error']
const getStatusLabel = ( text , type = 'neutral' , progress = 100 ) => {
let color = 'rgba(255, 255, 255, 0.0)' ;
if ( type === 'done' ) color = this . backgroundSuccessColor ;
else if ( type === 'error' ) color = this . backgroundFailedColor ;
return ( < div
className = { "status-label theme-border-primary " + type }
style = { { background : ` linear-gradient(90deg, ${ color } ${ progress } %, rgba(255, 255, 255, 0) ${ progress } %) ` } }
title = { text } > { text } < / div > ) ;
2016-11-02 22:32:24 +00:00
}
2016-11-02 14:56:23 +00:00
let statusLabel = "" ;
2016-11-09 21:13:43 +00:00
let statusIcon = statusCodes . icon ( task . status ) ;
2017-02-10 17:28:32 +00:00
let showEditLink = false ;
2016-11-02 22:32:24 +00:00
2016-11-09 21:13:43 +00:00
if ( task . last _error ) {
2018-12-07 20:13:49 +00:00
statusLabel = getStatusLabel ( task . last _error , 'error' ) ;
2019-02-20 21:42:20 +00:00
} else if ( ! task . processing _node && ! imported ) {
2017-02-10 17:28:32 +00:00
statusLabel = getStatusLabel ( "Set a processing node" ) ;
2016-11-02 22:32:24 +00:00
statusIcon = "fa fa-hourglass-3" ;
2017-02-10 17:28:32 +00:00
showEditLink = true ;
2019-06-26 22:41:09 +00:00
} else if ( task . partial && ! task . pending _action ) {
statusIcon = "fa fa-hourglass-3" ;
statusLabel = getStatusLabel ( "Waiting for image upload..." ) ;
2016-11-02 14:56:23 +00:00
} else {
2018-12-07 20:13:49 +00:00
let progress = 100 ;
let type = 'done' ;
if ( task . pending _action === pendingActions . RESIZE ) {
progress = task . resize _progress * 100 ;
} else if ( task . status === null ) {
progress = task . upload _progress * 100 ;
} else if ( task . status === statusCodes . RUNNING ) {
progress = task . running _progress * 100 ;
} else if ( task . status === statusCodes . FAILED ) {
type = 'error' ;
} else if ( task . status !== statusCodes . COMPLETED ) {
type = 'neutral' ;
}
statusLabel = getStatusLabel ( status , type , progress ) ;
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" >
2018-07-27 18:18:03 +00:00
< div className = "col-sm-5 name" >
2016-10-27 16:26:15 +00:00
< 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 >
2018-07-27 18:18:03 +00:00
< div className = "col-sm-1 details" >
2016-11-09 21:13:43 +00:00
< i className = "fa fa-image" > < / i > { task . images _count }
2018-10-18 12:44:07 +00:00
< / div >
2018-07-27 18:18:03 +00:00
< div className = "col-sm-2 details" >
2016-11-02 22:32:24 +00:00
< i className = "fa fa-clock-o" > < / i > { this . hoursMinutesSecs ( this . state . time ) }
< / div >
2018-07-27 18:18:03 +00:00
< div className = "col-sm-3" >
2018-10-18 12:44:07 +00:00
{ showEditLink ?
2017-02-10 17:28:32 +00:00
< a href = "javascript:void(0);" onClick = { this . startEditing } > { statusLabel } < / a >
: statusLabel }
2016-11-02 14:56:23 +00:00
< / div >
2018-07-27 18:18:03 +00:00
< div className = "col-sm-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 ;