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' ;
2021-08-04 17:09:27 +00:00
import MoveTaskDialog from './MoveTaskDialog' ;
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' ;
2020-12-16 16:35:54 +00:00
import Trans from './Trans' ;
import { _ , interpolate } from '../classes/gettext' ;
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
2021-08-04 17:09:27 +00:00
onDelete : PropTypes . func ,
onMove : PropTypes . func ,
hasPermission : PropTypes . func
2017-09-06 15:47:04 +00:00
}
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 : [ ] ,
2021-08-04 17:09:27 +00:00
view : "basic" ,
showMoveDialog : 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 ) ;
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 ( {
2020-12-16 16:35:54 +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 ( {
2020-12-16 16:35:54 +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 "" ;
2020-12-16 19:37:35 +00:00
else if ( options . length === 0 ) return "Default" ;
2016-11-04 18:19:18 +00:00
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 ) {
2020-12-16 16:35:54 +00:00
this . setState ( { friendlyTaskError : interpolate ( _ ( "It looks like there might be one of the following problems: %(problems)s You can read more about best practices for capturing good images %(link)s." ) , { problems : ` <ul>
< li > $ { _ ( "Not enough images" ) } < / li >
< li > $ { _ ( "Not enough overlap between images" ) } < / li >
< li > $ { _ ( "Images might be too blurry (common with phone cameras)" ) } < / li >
< li > $ { _ ( "The min-num-features task option is set too low, try increasing it by 25%" ) } < / li >
< / ul > ` , link: ` < 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 ) {
2020-12-16 16:35:54 +00:00
this . setState ( { friendlyTaskError : interpolate ( _ ( "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 %(link)s for more information." ) , { link : ` <a href='https://github.com/OpenDroneMap/WebODM#common-troubleshooting'> ${ _ ( "this page" ) } </a> ` } ) } ) ;
2018-09-04 15:19:22 +00:00
} else if ( line . indexOf ( "Child returned 127" ) !== - 1 ) {
2020-12-16 16:35:54 +00:00
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 ( ) ;
}
2021-08-04 17:09:27 +00:00
handleMoveTask = ( ) => {
this . setState ( { showMoveDialog : true } ) ;
}
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
const rfMap = { } ;
2021-02-05 21:28:45 +00:00
PipelineSteps . get ( ) . 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 ) {
2021-02-25 17:43:30 +00:00
rfMap [ rfParam ] . label = interpolate ( _ ( "From %(stage)s" ) , { stage : rfMap [ rfParam ] . label } ) ;
2018-01-25 01:09:59 +00:00
rfMap [ rfParam ] . onClick = this . genRestartAction ( rfParam ) ;
}
2020-07-27 16:14:23 +00:00
let items = task . can _rerun _from
2018-01-24 22:04:53 +00:00
. map ( rf => rfMap [ rf ] )
. filter ( rf => rf !== undefined ) ;
2020-07-27 16:14:23 +00:00
if ( items . length > 0 && [ statusCodes . CANCELED , statusCodes . FAILED ] . indexOf ( task . status ) !== - 1 ) {
// Add resume "pseudo button" to help users understand
// how to resume a task that failed for memory/disk issues.
items . unshift ( {
2020-12-16 16:35:54 +00:00
label : _ ( "Resume Processing" ) ,
2020-07-27 16:14:23 +00:00
icon : "fa fa-bolt" ,
onClick : this . genRestartAction ( task . can _rerun _from [ task . can _rerun _from . length - 1 ] )
} ) ;
}
return items ;
2018-01-24 22:04:53 +00:00
}
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 } ) ;
} ,
2020-12-16 16:35:54 +00:00
defaultError : _ ( "Cannot restart task." )
2018-01-25 01:09:59 +00:00
}
) ;
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 ( {
2020-12-16 16:35:54 +00:00
actionError : interpolate ( _ ( "Cannot restart task from (stage)s." ) , { stage : value || "the start" } ) ,
2018-01-25 01:09:59 +00:00
actionButtonsDisabled : false
} ) ;
} ) ;
} ;
return ( ) => {
setTaskRerunFrom ( rerunFrom )
. then ( restartAction ) ;
} ;
}
2021-08-04 20:20:51 +00:00
moveTaskAction = ( formData ) => {
if ( formData . project !== this . state . task . project ) {
return $ . ajax ( {
url : ` /api/projects/ ${ this . state . task . project } /tasks/ ${ this . state . task . id } / ` ,
contentType : 'application/json' ,
data : JSON . stringify ( formData ) ,
dataType : 'json' ,
type : 'PATCH'
} ) . done ( this . props . onMove ) ;
} else return false ;
}
2016-10-26 22:26:03 +00:00
render ( ) {
2016-11-09 21:13:43 +00:00
const task = this . state . task ;
2020-12-16 16:35:54 +00:00
const name = task . name !== null ? task . name : interpolate ( _ ( "Task #%(number)s" ) , { number : 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 ) ;
2020-12-16 16:35:54 +00:00
if ( status === "" ) status = _ ( "Uploading images to processing node" ) ;
2017-05-02 00:07:48 +00:00
2020-12-16 16:35:54 +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 ) {
2020-12-16 16:35:54 +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
2020-12-16 16:35:54 +00:00
addActionButton ( " " + _ ( "View 3D Model" ) , "btn-primary" , "fa fa-cube" , ( ) => {
2017-03-15 14:58:06 +00:00
location . href = ` /3d/project/ ${ task . project } /task/ ${ task . id } / ` ;
} ) ;
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
2020-12-16 16:35:54 +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
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 || "" ;
2021-02-05 20:09:30 +00:00
let buttonHtml = ( < button type = "button" className = { "btn btn-sm " + button . className } onClick = { button . onClick } disabled = { disabled } >
< i className = { button . icon } > < / i >
{ button . label }
< / button > ) ;
if ( subItems . length > 0 ) {
// The button expands sub items
buttonHtml = ( < button type = "button" className = { "btn btn-sm " + button . className } data - toggle = "dropdown" disabled = { disabled } >
< i className = { button . icon } > < / i >
{ button . label }
< / button > ) ;
}
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 } >
2021-02-05 20:09:30 +00:00
{ buttonHtml }
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 > ) ;
2021-07-29 16:03:54 +00:00
const stats = task . statistics ;
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" >
2020-12-16 16:35:54 +00:00
< div className = "console-output-label" > { _ ( "Task Output:" ) } < / div > < ul className = "list-inline" >
2019-05-20 14:05:56 +00:00
< li >
< div className = "btn-group btn-toggle" >
2020-12-16 16:35:54 +00:00
< 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 >
2019-05-20 14:05:56 +00:00
< / 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" >
2020-12-16 16:35:54 +00:00
< strong > { _ ( "Created on:" ) } < / strong > { ( new Date ( task . created _at ) ) . toLocaleString ( ) } < br / >
2019-05-20 14:05:56 +00:00
< / div >
< div className = "labels" >
2021-02-25 17:43:30 +00:00
< strong > { _ ( "Processing Node:" ) } < / strong > { task . processing _node _name || "-" } ( { task . auto _processing _node ? _ ( "auto" ) : _ ( "manual" ) } ) < br / >
2019-05-20 14:05:56 +00:00
< / div >
{ Array . isArray ( task . options ) ?
< div className = "labels" >
2020-12-16 16:35:54 +00:00
< strong > { _ ( "Options:" ) } < / strong > { this . optionsToList ( task . options ) } < br / >
2019-05-20 14:05:56 +00:00
< / div >
: "" }
2021-07-29 16:03:54 +00:00
{ stats && stats . gsd ?
< div className = "labels" >
< strong > { _ ( "Average GSD:" ) } < / strong > { stats . gsd . toFixed ( 2 ) } cm < br / >
< / div > : "" }
{ stats && stats . area ?
< div className = "labels" >
< strong > { _ ( "Area:" ) } < / strong > { stats . area . toFixed ( 2 ) } m & sup2 ; < br / >
< / div > : "" }
{ stats && stats . pointcloud && stats . pointcloud . points ?
< div className = "labels" >
2021-07-29 16:16:46 +00:00
< strong > { _ ( "Reconstructed Points:" ) } < / strong > { stats . pointcloud . points . toLocaleString ( ) } < br / >
2021-07-29 16:03:54 +00:00
< / div > : "" }
2019-05-20 14:05:56 +00:00
< / 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 ?
2020-12-16 16:35:54 +00:00
< 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 ?
2020-12-16 16:35:54 +00:00
< div className = "task-warning" > < i className = "fa fa-support" > < / i > < Trans params = { { memlink : ` <a href=" ${ memoryErrorLink } " target='_blank'> ${ _ ( "enough RAM allocated" ) } </a> ` , cloudlink : ` <a href='https://www.opendronemap.org/webodm/lightning/' target='_blank'> ${ _ ( "cloud processing node" ) } </a> ` } } > { _ ( "It looks like your processing node ran out of memory. If you are using docker, make sure that your docker environment has %(memlink)s. 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 options. You can also try to use a %(cloudlink)s." ) } < / Trans > < / 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" >
2020-12-16 19:37:35 +00:00
< Trans params = { { link1 : ` <a href="https://www.dronedb.app/" target="_blank">DroneDB</a> ` , link2 : ` <a href="https://drive.google.com/drive/u/0/" target="_blank">Google Drive</a> ` , open _a _topic : ` <a href="http://community.opendronemap.org/c/webodm" target="_blank"> ${ _ ( "open a topic" ) } </a> ` , } } > { _ ( "\"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 Task Options and sometimes it might be a bug! If you need help, upload your images somewhere like %(link1)s or %(link2)s and %(open_a_topic)s on our community forum, making sure to include a copy of your task's output. Our awesome contributors will try to help you!" ) } < / Trans > < i className = "far fa-smile" > < / 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
}
2021-08-04 17:09:27 +00:00
let statusIcon = statusCodes . icon ( task . status ) ;
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 } %) ` } }
2021-08-04 17:09:27 +00:00
title = { text } > < i className = { statusIcon } > < / i > { text } < / div > ) ;
2016-11-02 22:32:24 +00:00
}
2016-11-02 14:56:23 +00:00
let statusLabel = "" ;
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 ) {
2020-12-16 19:37:35 +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" ;
2020-12-16 19:37:35 +00:00
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
}
2021-08-04 17:09:27 +00:00
const taskActions = [ ] ;
const addTaskAction = ( label , icon , onClick ) => {
taskActions . push (
< li key = { label } > < a href = "javascript:void(0)" onClick = { onClick } > < i className = { icon } > < / i > { label } < / a > < / li >
) ;
} ;
if ( [ statusCodes . QUEUED , statusCodes . RUNNING , null ] . indexOf ( task . status ) !== - 1 &&
( task . processing _node || imported ) && this . props . hasPermission ( "change" ) ) {
addTaskAction ( _ ( "Cancel" ) , "glyphicon glyphicon-remove-circle" , this . genActionApiCall ( "cancel" , { defaultError : _ ( "Cannot cancel task." ) } ) ) ;
}
// Ability to change options
const editable = [ statusCodes . FAILED , statusCodes . COMPLETED , statusCodes . CANCELED ] . indexOf ( task . status ) !== - 1 ;
2021-08-04 20:20:51 +00:00
if ( this . props . hasPermission ( "change" ) ) {
2021-08-04 17:09:27 +00:00
if ( editable || ( ! task . processing _node ) ) {
taskActions . push ( < li key = "edit" > < a href = "javascript:void(0)" onClick = { this . startEditing } > < i className = "glyphicon glyphicon-pencil" > < / i > { _ ( "Edit" ) } < / a > < / li > ) ;
}
if ( editable ) {
taskActions . push (
< li key = "move" > < a href = "javascript:void(0)" onClick = { this . handleMoveTask } > < i className = "fa fa-arrows-alt" > < / i > { _ ( "Move" ) } < / a > < / li > ,
< li key = "duplicate" > < a href = "javascript:void(0)" > < i className = "fa fa-copy" > < / i > { _ ( "Duplicate" ) } < / a > < / li >
) ;
}
}
if ( this . props . hasPermission ( "delete" ) ) {
taskActions . push (
< li key = "sep" role = "separator" className = "divider" > < / li > ,
) ;
addTaskAction ( _ ( "Delete" ) , "glyphicon glyphicon-trash" , this . genActionApiCall ( "remove" , {
confirm : _ ( "All information related to this task, including images, maps and models will be deleted. Continue?" ) ,
defaultError : _ ( "Cannot delete task." )
} ) ) ;
}
2016-10-26 22:26:03 +00:00
return (
< div className = "task-list-item" >
2021-08-04 17:09:27 +00:00
{ this . state . showMoveDialog ?
< MoveTaskDialog
task = { task }
ref = { ( domNode ) => { this . moveTaskDialog = domNode ; } }
onHide = { ( ) => this . setState ( { showMoveDialog : false } ) }
2021-08-04 20:20:51 +00:00
saveAction = { this . moveTaskAction }
2021-08-04 17:09:27 +00:00
/ >
: "" }
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" >
2019-11-07 21:24:02 +00:00
< i onClick = { this . toggleExpanded } className = { "clickable far " + ( this . state . expanded ? "fa-minus-square" : " fa-plus-square" ) } > < / i > < a href = "javascript:void(0);" onClick = { this . toggleExpanded } > { name } < / a >
2016-10-27 16:26:15 +00:00
< / div >
2018-07-27 18:18:03 +00:00
< div className = "col-sm-1 details" >
2019-11-07 16:22:49 +00:00
< i className = "far 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" >
2019-11-07 16:22:49 +00:00
< i className = "far fa-clock" > < / i > { this . hoursMinutesSecs ( this . state . time ) }
2016-11-02 22:32:24 +00:00
< / 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" >
2021-08-04 17:09:27 +00:00
{ taskActions . length > 0 ?
< div className = "btn-group" >
< button disabled = { this . state . actionButtonsDisabled } className = "btn task-actions btn-secondary btn-xs dropdown-toggle" type = "button" data - toggle = "dropdown" aria - haspopup = "true" aria - expanded = "false" >
< i className = "fa fa-ellipsis-h" > < / i >
< / button >
< ul className = "dropdown-menu dropdown-menu-right" >
{ taskActions }
< / ul >
< / 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 ;