kopia lustrzana https://github.com/OpenDroneMap/WebODM
Started rewriting task item UI, added upload tracking progress
rodzic
683dbd6bb3
commit
8cff925b44
|
@ -0,0 +1,27 @@
|
|||
# Generated by Django 2.0.3 on 2018-12-05 16:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('app', '0021_auto_20180726_1746'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='task',
|
||||
name='ground_control_points',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='resize_progress',
|
||||
field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images."),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='upload_progress',
|
||||
field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node."),
|
||||
),
|
||||
]
|
|
@ -2,6 +2,7 @@ import logging
|
|||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
import time
|
||||
import uuid as uuid_module
|
||||
|
||||
import json
|
||||
|
@ -155,7 +156,6 @@ class Task(models.Model):
|
|||
options = fields.JSONField(default=dict(), blank=True, help_text="Options that are being used to process this task", validators=[validate_task_options])
|
||||
available_assets = fields.ArrayField(models.CharField(max_length=80), default=list(), blank=True, help_text="List of available assets to download")
|
||||
console_output = models.TextField(null=False, default="", blank=True, help_text="Console output of the OpenDroneMap's process")
|
||||
ground_control_points = models.FileField(null=True, blank=True, upload_to=gcp_directory_path, help_text="Optional Ground Control Points file to use for processing")
|
||||
|
||||
orthophoto_extent = GeometryField(null=True, blank=True, srid=4326, help_text="Extent of the orthophoto created by OpenDroneMap")
|
||||
dsm_extent = GeometryField(null=True, blank=True, srid=4326, help_text="Extent of the DSM created by OpenDroneMap")
|
||||
|
@ -168,6 +168,12 @@ class Task(models.Model):
|
|||
public = models.BooleanField(default=False, help_text="A flag indicating whether this task is available to the public")
|
||||
resize_to = models.IntegerField(default=-1, help_text="When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.")
|
||||
|
||||
upload_progress = models.FloatField(default=0.0,
|
||||
help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node.",
|
||||
blank=True)
|
||||
resize_progress = models.FloatField(default=0.0,
|
||||
help_text="Value between 0 and 1 indicating the resize progress of this task's images.",
|
||||
blank=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Task, self).__init__(*args, **kwargs)
|
||||
|
@ -333,10 +339,14 @@ class Task(models.Model):
|
|||
|
||||
images = [image.path() for image in self.imageupload_set.all()]
|
||||
|
||||
# Track upload progress, but limit the number of DB updates
|
||||
# to every 2 seconds (and always record the 100% progress)
|
||||
last_update = 0
|
||||
def callback(progress):
|
||||
# TODO: add step_progress field in task to track progress
|
||||
pass
|
||||
|
||||
nonlocal last_update
|
||||
if time.time() - last_update >= 2 or (progress >= 1.0 - 1e-6 and progress <= 1.0 + 1e-6):
|
||||
Task.objects.filter(pk=self.id).update(upload_progress=progress)
|
||||
last_update = time.time()
|
||||
|
||||
# This takes a while
|
||||
uuid = self.processing_node.process_new_task(images, self.name, self.options, callback)
|
||||
|
|
|
@ -264,4 +264,8 @@ footer{
|
|||
.full-height{
|
||||
height: calc(100vh - 110px);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.clearfix{
|
||||
clear: both;
|
||||
}
|
|
@ -24,6 +24,8 @@ class Console extends React.Component {
|
|||
this.setRef = this.setRef.bind(this);
|
||||
this.handleMouseOver = this.handleMouseOver.bind(this);
|
||||
this.handleMouseOut = this.handleMouseOut.bind(this);
|
||||
this.downloadTxt = this.downloadTxt.bind(this);
|
||||
this.copyTxt = this.copyTxt.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
@ -64,6 +66,7 @@ class Console extends React.Component {
|
|||
}
|
||||
|
||||
downloadTxt(filename="console.txt"){
|
||||
console.log(filename);
|
||||
function saveAs(uri, filename) {
|
||||
let link = document.createElement('a');
|
||||
if (typeof link.download === 'string') {
|
||||
|
@ -93,7 +96,7 @@ class Console extends React.Component {
|
|||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
console.log("Task output copied to clipboard");
|
||||
console.log("Output copied to clipboard");
|
||||
}
|
||||
|
||||
tearDownDynamicSource(){
|
||||
|
@ -140,22 +143,41 @@ class Console extends React.Component {
|
|||
}
|
||||
let i = 0;
|
||||
|
||||
return (
|
||||
<pre className={`console prettyprint
|
||||
${this.props.lang ? `lang-${this.props.lang}` : ""}
|
||||
${this.props.lines ? "linenums" : ""}`}
|
||||
style={{height: (this.props.height ? this.props.height : "auto")}}
|
||||
onMouseOver={this.handleMouseOver}
|
||||
onMouseOut={this.handleMouseOut}
|
||||
ref={this.setRef}
|
||||
>
|
||||
{this.state.lines.map(line => {
|
||||
if (this.props.lang) return (<div key={i++} dangerouslySetInnerHTML={prettyLine(line)}></div>);
|
||||
else return line + "\n";
|
||||
})}
|
||||
{"\n"}
|
||||
</pre>
|
||||
);
|
||||
let lines = this.state.lines;
|
||||
if (this.props.maximumLines && lines.length > this.props.maximumLines){
|
||||
lines = lines.slice(-this.props.maximumLines);
|
||||
lines.unshift(`... output truncated at ${this.props.maximumLines} lines ...`);
|
||||
}
|
||||
|
||||
const items = [
|
||||
<pre key="console" className={`console prettyprint
|
||||
${this.props.lang ? `lang-${this.props.lang}` : ""}
|
||||
${this.props.lines ? "linenums" : ""}
|
||||
${this.props.className || ""}`}
|
||||
style={{height: (this.props.height ? this.props.height : "auto")}}
|
||||
onMouseOver={this.handleMouseOver}
|
||||
onMouseOut={this.handleMouseOut}
|
||||
ref={this.setRef}
|
||||
>
|
||||
{lines.map(line => {
|
||||
if (this.props.lang) return (<div key={i++} dangerouslySetInnerHTML={prettyLine(line)}></div>);
|
||||
else return line + "\n";
|
||||
})}
|
||||
{"\n"}
|
||||
</pre>];
|
||||
|
||||
if (this.props.showConsoleButtons){
|
||||
items.push(<div key="buttons" className="console-buttons">
|
||||
<a href="javascript:void(0);" onClick={() => this.downloadTxt()} className="btn btn-sm btn-primary" title="Download To File">
|
||||
<i className="fa fa-download"></i>
|
||||
</a>
|
||||
<a href="javascript:void(0);" onClick={this.copyTxt} className="btn btn-sm btn-primary" title="Copy To Clipboard">
|
||||
<i className="fa fa-clipboard"></i>
|
||||
</a>
|
||||
</div>);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
export default {
|
||||
get: function(){
|
||||
return [{
|
||||
action: "dataset",
|
||||
label: "Load Dataset",
|
||||
icon: "fa fa-database",
|
||||
beginsWith: "Running ODM Load Dataset Cell",
|
||||
endsWith: "Running ODM Load Dataset Cell - Finished"
|
||||
},
|
||||
{
|
||||
action: "opensfm",
|
||||
label: "Structure From Motion",
|
||||
icon: "fa fa-camera",
|
||||
beginsWith: "Running ODM OpenSfM Cell",
|
||||
endsWith: "Running ODM Meshing Cell"
|
||||
},
|
||||
{
|
||||
action: "odm_meshing",
|
||||
label: "Meshing",
|
||||
icon: "fa fa-cube",
|
||||
beginsWith: "Running ODM Meshing Cell",
|
||||
endsWith: "Running ODM Meshing Cell - Finished"
|
||||
},
|
||||
{
|
||||
action: "mvs_texturing",
|
||||
label: "Texturing",
|
||||
icon: "fa fa-connectdevelop",
|
||||
beginsWith: "Running MVS Texturing Cell",
|
||||
endsWith: "Running ODM Texturing Cell - Finished"
|
||||
},
|
||||
{
|
||||
action: "odm_georeferencing",
|
||||
label: "Georeferencing",
|
||||
icon: "fa fa-globe",
|
||||
beginsWith: "Running ODM Georeferencing Cell",
|
||||
endsWith: "Running ODM Georeferencing Cell - Finished"
|
||||
},
|
||||
{
|
||||
action: "odm_dem",
|
||||
label: "DEM",
|
||||
icon: "fa fa-area-chart",
|
||||
beginsWith: "Running ODM DEM Cell",
|
||||
endsWith: "Running ODM DEM Cell - Finished"
|
||||
},
|
||||
{
|
||||
action: "odm_orthophoto",
|
||||
label: "Orthophoto",
|
||||
icon: "fa fa-map-o",
|
||||
beginsWith: "Running ODM Orthophoto Cell",
|
||||
endsWith: "Running ODM OrthoPhoto Cell - Finished"
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
|
@ -45,7 +45,7 @@ class AssetDownloadButtons extends React.Component {
|
|||
{assetDownloads.map((asset, i) => {
|
||||
if (!asset.separator){
|
||||
return (<li key={i}>
|
||||
<a href="javascript:void(0);" onClick={this.downloadAsset(asset)}><i className={asset.icon}></i> {asset.label}</a>
|
||||
<a href="javascript:void(0);" onClick={this.downloadAsset(asset)}><i className={asset.icon + " fa-fw"}></i> {asset.label}</a>
|
||||
</li>);
|
||||
}else{
|
||||
return (<li key={i} className="divider"></li>);
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import '../css/BasicTaskView.scss';
|
||||
import update from 'immutability-helper';
|
||||
import RerunFromParams from '../classes/RerunFromParams';
|
||||
import StatusCodes from '../classes/StatusCodes';
|
||||
import $ from 'jquery';
|
||||
|
||||
class BasicTaskView extends React.Component {
|
||||
static defaultProps = {};
|
||||
|
||||
static propTypes = {
|
||||
source: PropTypes.oneOfType([
|
||||
PropTypes.string.isRequired,
|
||||
PropTypes.func.isRequired
|
||||
]),
|
||||
taskStatus: PropTypes.number,
|
||||
onAddLines: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props){
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
lines: [],
|
||||
currentRf: 0,
|
||||
rf: RerunFromParams.get()
|
||||
};
|
||||
|
||||
// Add a post processing step
|
||||
this.state.rf.push({
|
||||
action: "postprocessing",
|
||||
label: "Post Processing",
|
||||
icon: "fa fa-file-archive-o",
|
||||
beginsWith: "Running ODM OrthoPhoto Cell - Finished",
|
||||
endsWith: null
|
||||
});
|
||||
|
||||
this.addLines = this.addLines.bind(this);
|
||||
this.setupDynamicSource = this.setupDynamicSource.bind(this);
|
||||
this.reset = this.reset.bind(this);
|
||||
this.tearDownDynamicSource = this.tearDownDynamicSource.bind(this);
|
||||
this.getRfEndStatus = this.getRfEndStatus.bind(this);
|
||||
this.getRfRunningStatus = this.getRfRunningStatus.bind(this);
|
||||
this.suffixFor = this.suffixFor.bind(this);
|
||||
this.updateRfState = this.updateRfState.bind(this);
|
||||
this.getInitialStatus = this.getInitialStatus.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.reset();
|
||||
}
|
||||
|
||||
setupDynamicSource(){
|
||||
const updateFromSource = () => {
|
||||
let sourceUrl = typeof this.props.source === 'function' ?
|
||||
this.props.source(this.state.lines.length) :
|
||||
this.props.source;
|
||||
|
||||
// Fetch
|
||||
this.sourceRequest = $.get(sourceUrl, text => {
|
||||
if (text !== ""){
|
||||
let lines = text.split("\n");
|
||||
this.addLines(lines);
|
||||
}
|
||||
})
|
||||
.always((_, textStatus) => {
|
||||
if (textStatus !== "abort" && this.props.refreshInterval !== undefined){
|
||||
this.sourceTimeout = setTimeout(updateFromSource, this.props.refreshInterval);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
updateFromSource();
|
||||
}
|
||||
|
||||
getRfEndStatus(){
|
||||
return this.props.taskStatus === StatusCodes.COMPLETED ?
|
||||
'completed' :
|
||||
'errored';
|
||||
}
|
||||
|
||||
getRfRunningStatus(){
|
||||
return [StatusCodes.RUNNING, StatusCodes.QUEUED].indexOf(this.props.taskStatus) !== -1 ?
|
||||
'running' :
|
||||
'errored';
|
||||
}
|
||||
|
||||
getInitialStatus(){
|
||||
if ([StatusCodes.QUEUED, StatusCodes.RUNNING].indexOf(this.props.taskStatus) !== -1){
|
||||
return 'queued';
|
||||
}else{
|
||||
return this.getRfEndStatus();
|
||||
}
|
||||
}
|
||||
|
||||
reset(){
|
||||
this.state.rf.forEach(p => {
|
||||
p.state = this.getInitialStatus();
|
||||
});
|
||||
|
||||
this.tearDownDynamicSource();
|
||||
this.setState({lines: [], currentRf: 0});
|
||||
this.setupDynamicSource();
|
||||
}
|
||||
|
||||
tearDownDynamicSource(){
|
||||
if (this.sourceTimeout) clearTimeout(this.sourceTimeout);
|
||||
if (this.sourceRequest) this.sourceRequest.abort();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps){
|
||||
let taskFailed = [StatusCodes.RUNNING, StatusCodes.QUEUED].indexOf(prevProps.taskStatus) !== -1 &&
|
||||
[StatusCodes.FAILED, StatusCodes.CANCELED].indexOf(this.props.taskStatus) !== -1;
|
||||
this.updateRfState(taskFailed);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
this.tearDownDynamicSource();
|
||||
}
|
||||
|
||||
addLines(lines){
|
||||
if (!Array.isArray(lines)) lines = [lines];
|
||||
|
||||
let currentRf = this.state.currentRf;
|
||||
|
||||
const updateRf = (rfIndex, line) => {
|
||||
const current = this.state.rf[rfIndex];
|
||||
if (!current) return;
|
||||
|
||||
if (current.beginsWith && line.endsWith(current.beginsWith)){
|
||||
current.state = this.getRfRunningStatus();
|
||||
|
||||
// Set previous as done
|
||||
if (this.state.rf[rfIndex - 1]){
|
||||
this.state.rf[rfIndex - 1].state = 'completed';
|
||||
}
|
||||
}else if (current.endsWith && line.endsWith(current.endsWith)){
|
||||
current.state = this.getRfEndStatus();
|
||||
|
||||
// Check next
|
||||
updateRf(rfIndex + 1, line);
|
||||
|
||||
currentRf = rfIndex + 1;
|
||||
}
|
||||
};
|
||||
|
||||
lines.forEach(line => {
|
||||
updateRf(currentRf, line);
|
||||
});
|
||||
|
||||
this.setState(update(this.state, {
|
||||
lines: {$push: lines}
|
||||
}));
|
||||
this.setState({
|
||||
rf: this.state.rf,
|
||||
currentRf
|
||||
});
|
||||
this.updateRfState();
|
||||
|
||||
if (this.props.onAddLines) this.props.onAddLines(lines);
|
||||
}
|
||||
|
||||
updateRfState(taskFailed){
|
||||
// If the task has just failed, update all items that were either running or in queued state
|
||||
if (taskFailed){
|
||||
this.state.rf.forEach(p => {
|
||||
if (p.state === 'queued' || p.state === 'running') p.state = this.getInitialStatus();
|
||||
});
|
||||
}
|
||||
|
||||
// The last is always dependent on the task status
|
||||
this.state.rf[this.state.rf.length - 1].state = this.getInitialStatus();
|
||||
|
||||
}
|
||||
|
||||
suffixFor(state){
|
||||
if (state === 'running'){
|
||||
return (<span>...</span>);
|
||||
}else if (state === 'completed'){
|
||||
return (<i className="fa fa-check"></i>);
|
||||
}else if (state === 'errored'){
|
||||
return (<i className="fa fa-times"></i>);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div className="basic-task-view">
|
||||
{this.state.rf.map(p => {
|
||||
return (<div key={p.action} className={p.state + " processing-step"}>
|
||||
<i className={p.icon + " fa-fw"}></i> {p.label} {this.suffixFor(p.state)}
|
||||
</div>);
|
||||
})}
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
export default BasicTaskView;
|
|
@ -9,6 +9,8 @@ import AssetDownloadButtons from './AssetDownloadButtons';
|
|||
import HistoryNav from '../classes/HistoryNav';
|
||||
import PropTypes from 'prop-types';
|
||||
import TaskPluginActionButtons from './TaskPluginActionButtons';
|
||||
import RerunFromParams from '../classes/RerunFromParams';
|
||||
import BasicTaskView from './BasicTaskView';
|
||||
|
||||
class TaskListItem extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -32,7 +34,8 @@ class TaskListItem extends React.Component {
|
|||
editing: false,
|
||||
memoryError: false,
|
||||
friendlyTaskError: "",
|
||||
pluginActionButtons: []
|
||||
pluginActionButtons: [],
|
||||
view: "basic"
|
||||
}
|
||||
|
||||
for (let k in props.data){
|
||||
|
@ -44,9 +47,8 @@ class TaskListItem extends React.Component {
|
|||
this.stopEditing = this.stopEditing.bind(this);
|
||||
this.startEditing = this.startEditing.bind(this);
|
||||
this.checkForCommonErrors = this.checkForCommonErrors.bind(this);
|
||||
this.downloadTaskOutput = this.downloadTaskOutput.bind(this);
|
||||
this.copyTaskOutput = this.copyTaskOutput.bind(this);
|
||||
this.handleEditTaskSave = this.handleEditTaskSave.bind(this);
|
||||
this.setView = this.setView.bind(this);
|
||||
}
|
||||
|
||||
shouldRefresh(){
|
||||
|
@ -69,6 +71,12 @@ class TaskListItem extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
setView(type){
|
||||
return () => {
|
||||
this.setState({view: type});
|
||||
}
|
||||
}
|
||||
|
||||
unloadTimer(){
|
||||
if (this.processingTimeInterval){
|
||||
clearInterval(this.processingTimeInterval);
|
||||
|
@ -96,6 +104,7 @@ class TaskListItem extends React.Component {
|
|||
if (oldStatus !== this.state.task.status){
|
||||
if (this.state.task.status === statusCodes.RUNNING){
|
||||
if (this.console) this.console.clear();
|
||||
if (this.basicView) this.basicView.reset();
|
||||
this.loadTimer(this.state.task.processing_time);
|
||||
}else{
|
||||
this.setState({time: this.state.task.processing_time});
|
||||
|
@ -205,14 +214,6 @@ class TaskListItem extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
downloadTaskOutput(){
|
||||
this.console.downloadTxt("task_output.txt");
|
||||
}
|
||||
|
||||
copyTaskOutput(){
|
||||
this.console.copyTxt();
|
||||
}
|
||||
|
||||
optionsToList(options){
|
||||
if (!Array.isArray(options)) return "";
|
||||
else if (options.length === 0) return "Default";
|
||||
|
@ -269,35 +270,13 @@ class TaskListItem extends React.Component {
|
|||
const { task } = this.state;
|
||||
|
||||
// Map rerun-from parameters to display items
|
||||
const rfMap = {
|
||||
"odm_meshing": {
|
||||
label: "From Meshing",
|
||||
icon: "fa fa-cube"
|
||||
},
|
||||
|
||||
"mvs_texturing": {
|
||||
label: "From Texturing",
|
||||
icon: "fa fa-connectdevelop"
|
||||
},
|
||||
|
||||
"odm_georeferencing": {
|
||||
label: "From Georeferencing",
|
||||
icon: "fa fa-globe"
|
||||
},
|
||||
|
||||
"odm_dem": {
|
||||
label: "From DEM",
|
||||
icon: "fa fa-area-chart"
|
||||
},
|
||||
|
||||
"odm_orthophoto": {
|
||||
label: "From Orthophoto",
|
||||
icon: "fa fa-map-o"
|
||||
}
|
||||
};
|
||||
// (remove the first item so that 'dataset' is not displayed)
|
||||
const rfMap = {};
|
||||
RerunFromParams.get().slice(1).forEach(rf => rfMap[rf.action] = rf);
|
||||
|
||||
// Create onClick handlers
|
||||
for (let rfParam in rfMap){
|
||||
rfMap[rfParam].label = "From " + rfMap[rfParam].label;
|
||||
rfMap[rfParam].onClick = this.genRestartAction(rfParam);
|
||||
}
|
||||
|
||||
|
@ -468,7 +447,7 @@ class TaskListItem extends React.Component {
|
|||
data-toggle="dropdown"><span className="caret"></span></button>,
|
||||
<ul key="dropdown-menu" className="dropdown-menu">
|
||||
{subItems.map(subItem => <li key={subItem.label}>
|
||||
<a href="javascript:void(0);" onClick={subItem.onClick}><i className={subItem.icon}></i>{subItem.label}</a>
|
||||
<a href="javascript:void(0);" onClick={subItem.onClick}><i className={subItem.icon + ' fa-fw '}></i>{subItem.label}</a>
|
||||
</li>)}
|
||||
</ul>]}
|
||||
</div>);
|
||||
|
@ -501,23 +480,35 @@ class TaskListItem extends React.Component {
|
|||
|
||||
</div>
|
||||
<div className="col-md-8">
|
||||
<Console
|
||||
source={this.consoleOutputUrl}
|
||||
refreshInterval={this.shouldRefresh() ? 3000 : undefined}
|
||||
autoscroll={true}
|
||||
height={200}
|
||||
ref={domNode => this.console = domNode}
|
||||
onAddLines={this.checkForCommonErrors}
|
||||
/>
|
||||
|
||||
<div className="console-buttons">
|
||||
<a href="javascript:void(0);" onClick={this.downloadTaskOutput} className="btn btn-sm btn-primary" title="Download Task Output">
|
||||
<i className="fa fa-download"></i>
|
||||
</a>
|
||||
<a href="javascript:void(0);" onClick={this.copyTaskOutput} className="btn btn-sm btn-primary" title="Copy Task Output">
|
||||
<i className="fa fa-clipboard"></i>
|
||||
</a>
|
||||
<div className="switch-view text-right pull-right">
|
||||
<i className="fa fa-list-ul"></i> <a href="javascript:void(0);" onClick={this.setView("basic")}
|
||||
className={this.state.view === 'basic' ? "selected" : ""}>Basic</a>
|
||||
|
|
||||
<i className="fa fa-desktop"></i> <a href="javascript:void(0);" onClick={this.setView("console")}
|
||||
className={this.state.view === 'console' ? "selected" : ""}>Console</a>
|
||||
</div>
|
||||
|
||||
{this.state.view === 'console' ?
|
||||
<Console
|
||||
className="clearfix"
|
||||
source={this.consoleOutputUrl}
|
||||
refreshInterval={this.shouldRefresh() ? 3000 : undefined}
|
||||
autoscroll={true}
|
||||
height={200}
|
||||
ref={domNode => this.console = domNode}
|
||||
onAddLines={this.checkForCommonErrors}
|
||||
showConsoleButtons={true}
|
||||
maximumLines={500}
|
||||
/> : ""}
|
||||
|
||||
{this.state.view === 'basic' ?
|
||||
<BasicTaskView
|
||||
source={this.consoleOutputUrl}
|
||||
ref={domNode => this.basicView = domNode}
|
||||
refreshInterval={this.shouldRefresh() ? 3000 : undefined}
|
||||
onAddLines={this.checkForCommonErrors}
|
||||
taskStatus={task.status}
|
||||
/> : ""}
|
||||
|
||||
{showMemoryErrorWarning ?
|
||||
<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>.</span></div> : ""}
|
||||
|
@ -532,7 +523,7 @@ class TaskListItem extends React.Component {
|
|||
<li>Increase the <b>min-num-features</b> option, especially if your images have lots of vegetation</li>
|
||||
</ul>
|
||||
Still not working? 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
|
||||
sure to include a <a href="javascript:void(0);" onClick={this.downloadTaskOutput}>copy of your task's output</a> (the one you see above <i className="fa fa-arrow-up"></i>, click to <a href="javascript:void(0);" onClick={this.downloadTaskOutput}>download</a> it). Our awesome contributors will try to help you! <i className="fa fa-smile-o"></i>
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
: ""}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import BasicTaskView from '../BasicTaskView';
|
||||
|
||||
describe('<BasicTaskView />', () => {
|
||||
it('renders without exploding', () => {
|
||||
const wrapper = mount(<BasicTaskView source="http://localhost/output" taskStatus={40} />);
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
})
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
.basic-task-view{
|
||||
margin-bottom: 8px;
|
||||
|
||||
.processing-step{
|
||||
opacity: 0.7;
|
||||
|
||||
&.completed{
|
||||
opacity: 1;
|
||||
/* font-weight: bold; */
|
||||
}
|
||||
&.running{
|
||||
opacity: 1;
|
||||
animation: pulse ease-in-out 1.4833s infinite;
|
||||
}
|
||||
&.queued{
|
||||
|
||||
}
|
||||
&.errored{
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.console-buttons{
|
||||
margin-left: 16px;
|
||||
margin-bottom: 16px;
|
||||
float: right;
|
||||
text-align: right;
|
||||
a{
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
|
@ -97,13 +97,23 @@
|
|||
display: inline;
|
||||
}
|
||||
|
||||
.console-buttons{
|
||||
margin-left: 16px;
|
||||
margin-bottom: 16px;
|
||||
float: right;
|
||||
text-align: right;
|
||||
.switch-view{
|
||||
margin-bottom: 8px;
|
||||
font-size: 90%;
|
||||
|
||||
a{
|
||||
margin-left: 4px;
|
||||
margin-right: 8px;
|
||||
&.selected{
|
||||
font-weight: bold;
|
||||
}
|
||||
&:last-child{
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
i{
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Ładowanie…
Reference in New Issue