kopia lustrzana https://github.com/OpenDroneMap/WebODM
Merge pull request #539 from aaj013/download-copy-task-outputs
Always show a button to download or copy to clipboard task outputspull/542/head
commit
a223b0968f
|
@ -28,7 +28,7 @@ class Console extends React.Component {
|
||||||
|
|
||||||
componentDidMount(){
|
componentDidMount(){
|
||||||
this.checkAutoscroll();
|
this.checkAutoscroll();
|
||||||
this.setupDynamicSource();
|
this.setupDynamicSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupDynamicSource(){
|
setupDynamicSource(){
|
||||||
|
@ -72,7 +72,7 @@ class Console extends React.Component {
|
||||||
|
|
||||||
//Firefox requires the link to be in the body
|
//Firefox requires the link to be in the body
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
|
|
||||||
//simulate click
|
//simulate click
|
||||||
link.click();
|
link.click();
|
||||||
|
|
||||||
|
@ -86,6 +86,16 @@ class Console extends React.Component {
|
||||||
saveAs("data:application/octet-stream," + encodeURIComponent(this.state.lines.join("\r\n")), filename);
|
saveAs("data:application/octet-stream," + encodeURIComponent(this.state.lines.join("\r\n")), filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyTxt(){
|
||||||
|
const el = document.createElement('textarea');
|
||||||
|
el.value = this.state.lines.join("\r\n");
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(el);
|
||||||
|
console.log("Task output copied to clipboard");
|
||||||
|
}
|
||||||
|
|
||||||
tearDownDynamicSource(){
|
tearDownDynamicSource(){
|
||||||
if (this.sourceTimeout) clearTimeout(this.sourceTimeout);
|
if (this.sourceTimeout) clearTimeout(this.sourceTimeout);
|
||||||
if (this.sourceRequest) this.sourceRequest.abort();
|
if (this.sourceRequest) this.sourceRequest.abort();
|
||||||
|
@ -131,8 +141,8 @@ class Console extends React.Component {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pre className={`console prettyprint
|
<pre className={`console prettyprint
|
||||||
${this.props.lang ? `lang-${this.props.lang}` : ""}
|
${this.props.lang ? `lang-${this.props.lang}` : ""}
|
||||||
${this.props.lines ? "linenums" : ""}`}
|
${this.props.lines ? "linenums" : ""}`}
|
||||||
style={{height: (this.props.height ? this.props.height : "auto")}}
|
style={{height: (this.props.height ? this.props.height : "auto")}}
|
||||||
onMouseOver={this.handleMouseOver}
|
onMouseOver={this.handleMouseOver}
|
||||||
|
@ -151,7 +161,7 @@ class Console extends React.Component {
|
||||||
|
|
||||||
$(function(){
|
$(function(){
|
||||||
$("[data-console]").each(function(){
|
$("[data-console]").each(function(){
|
||||||
window.ReactDOM.render(<Console
|
window.ReactDOM.render(<Console
|
||||||
lang={$(this).data("console-lang")}
|
lang={$(this).data("console-lang")}
|
||||||
height={$(this).data("console-height")}
|
height={$(this).data("console-height")}
|
||||||
autoscroll={typeof $(this).attr("autoscroll") !== 'undefined' && $(this).attr("autoscroll") !== false}
|
autoscroll={typeof $(this).attr("autoscroll") !== 'undefined' && $(this).attr("autoscroll") !== false}
|
||||||
|
|
|
@ -45,6 +45,7 @@ class TaskListItem extends React.Component {
|
||||||
this.startEditing = this.startEditing.bind(this);
|
this.startEditing = this.startEditing.bind(this);
|
||||||
this.checkForCommonErrors = this.checkForCommonErrors.bind(this);
|
this.checkForCommonErrors = this.checkForCommonErrors.bind(this);
|
||||||
this.downloadTaskOutput = this.downloadTaskOutput.bind(this);
|
this.downloadTaskOutput = this.downloadTaskOutput.bind(this);
|
||||||
|
this.copyTaskOutput = this.copyTaskOutput.bind(this);
|
||||||
this.handleEditTaskSave = this.handleEditTaskSave.bind(this);
|
this.handleEditTaskSave = this.handleEditTaskSave.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ class TaskListItem extends React.Component {
|
||||||
}else{
|
}else{
|
||||||
console.warn("Cannot refresh task: " + json);
|
console.warn("Cannot refresh task: " + json);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setAutoRefresh();
|
this.setAutoRefresh();
|
||||||
})
|
})
|
||||||
.fail(( _, __, errorThrown) => {
|
.fail(( _, __, errorThrown) => {
|
||||||
|
@ -132,7 +133,7 @@ class TaskListItem extends React.Component {
|
||||||
const expanded = !this.state.expanded;
|
const expanded = !this.state.expanded;
|
||||||
|
|
||||||
this.historyNav.toggleQSListItem("project_task_expanded", this.props.data.id, expanded);
|
this.historyNav.toggleQSListItem("project_task_expanded", this.props.data.id, expanded);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
expanded: expanded
|
expanded: expanded
|
||||||
});
|
});
|
||||||
|
@ -205,6 +206,10 @@ class TaskListItem extends React.Component {
|
||||||
this.console.downloadTxt("task_output.txt");
|
this.console.downloadTxt("task_output.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyTaskOutput(){
|
||||||
|
this.console.copyTxt();
|
||||||
|
}
|
||||||
|
|
||||||
optionsToList(options){
|
optionsToList(options){
|
||||||
if (!Array.isArray(options)) return "";
|
if (!Array.isArray(options)) return "";
|
||||||
else if (options.length === 0) return "Default";
|
else if (options.length === 0) return "Default";
|
||||||
|
@ -223,15 +228,15 @@ class TaskListItem extends React.Component {
|
||||||
|
|
||||||
checkForCommonErrors(lines){
|
checkForCommonErrors(lines){
|
||||||
for (let line of lines){
|
for (let line of lines){
|
||||||
if (line.indexOf("Killed") !== -1 ||
|
if (line.indexOf("Killed") !== -1 ||
|
||||||
line.indexOf("MemoryError") !== -1 ||
|
line.indexOf("MemoryError") !== -1 ||
|
||||||
line.indexOf("std::bad_alloc") !== -1 ||
|
line.indexOf("std::bad_alloc") !== -1 ||
|
||||||
line.indexOf("Child returned 137") !== -1 ||
|
line.indexOf("Child returned 137") !== -1 ||
|
||||||
line.indexOf("loky.process_executor.TerminatedWorkerError:") !== -1 ||
|
line.indexOf("loky.process_executor.TerminatedWorkerError:") !== -1 ||
|
||||||
line.indexOf("Failed to allocate memory") !== -1){
|
line.indexOf("Failed to allocate memory") !== -1){
|
||||||
this.setState({memoryError: true});
|
this.setState({memoryError: true});
|
||||||
}else if (line.indexOf("SVD did not converge") !== -1){
|
}else if (line.indexOf("SVD did not converge") !== -1){
|
||||||
this.setState({friendlyTaskError: `It looks like the images might have one of the following problems:
|
this.setState({friendlyTaskError: `It looks like the images might have one of the following problems:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Not enough images</li>
|
<li>Not enough images</li>
|
||||||
<li>Not enough overlap between images</li>
|
<li>Not enough overlap between images</li>
|
||||||
|
@ -327,7 +332,7 @@ class TaskListItem extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
options: task.options
|
options: task.options
|
||||||
};
|
};
|
||||||
|
@ -373,19 +378,19 @@ class TaskListItem extends React.Component {
|
||||||
let showOrthophotoMissingWarning = false,
|
let showOrthophotoMissingWarning = false,
|
||||||
showMemoryErrorWarning = this.state.memoryError && task.status == statusCodes.FAILED,
|
showMemoryErrorWarning = this.state.memoryError && task.status == statusCodes.FAILED,
|
||||||
showTaskWarning = this.state.friendlyTaskError !== "" && task.status == statusCodes.FAILED,
|
showTaskWarning = this.state.friendlyTaskError !== "" && task.status == statusCodes.FAILED,
|
||||||
showExitedWithCodeOneHints = task.last_error === "Process exited with code 1" &&
|
showExitedWithCodeOneHints = task.last_error === "Process exited with code 1" &&
|
||||||
!showMemoryErrorWarning &&
|
!showMemoryErrorWarning &&
|
||||||
!showTaskWarning &&
|
!showTaskWarning &&
|
||||||
task.status == statusCodes.FAILED,
|
task.status == statusCodes.FAILED,
|
||||||
memoryErrorLink = this.isMacOS() ? "http://stackoverflow.com/a/39720010" : "https://docs.docker.com/docker-for-windows/#advanced";
|
memoryErrorLink = this.isMacOS() ? "http://stackoverflow.com/a/39720010" : "https://docs.docker.com/docker-for-windows/#advanced";
|
||||||
|
|
||||||
let actionButtons = [];
|
let actionButtons = [];
|
||||||
const addActionButton = (label, className, icon, onClick, options = {}) => {
|
const addActionButton = (label, className, icon, onClick, options = {}) => {
|
||||||
actionButtons.push({
|
actionButtons.push({
|
||||||
className, icon, label, onClick, options
|
className, icon, label, onClick, options
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (task.status === statusCodes.COMPLETED){
|
if (task.status === statusCodes.COMPLETED){
|
||||||
if (task.available_assets.indexOf("orthophoto.tif") !== -1){
|
if (task.available_assets.indexOf("orthophoto.tif") !== -1){
|
||||||
addActionButton(" View Map", "btn-primary", "fa fa-globe", () => {
|
addActionButton(" View Map", "btn-primary", "fa fa-globe", () => {
|
||||||
|
@ -394,7 +399,7 @@ class TaskListItem extends React.Component {
|
||||||
}else{
|
}else{
|
||||||
showOrthophotoMissingWarning = true;
|
showOrthophotoMissingWarning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addActionButton(" View 3D Model", "btn-primary", "fa fa-cube", () => {
|
addActionButton(" View 3D Model", "btn-primary", "fa fa-cube", () => {
|
||||||
location.href = `/3d/project/${task.project}/task/${task.id}/`;
|
location.href = `/3d/project/${task.project}/task/${task.id}/`;
|
||||||
});
|
});
|
||||||
|
@ -417,10 +422,10 @@ class TaskListItem extends React.Component {
|
||||||
|
|
||||||
if ([statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1 &&
|
if ([statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1 &&
|
||||||
task.processing_node){
|
task.processing_node){
|
||||||
// By default restart reruns every pipeline
|
// By default restart reruns every pipeline
|
||||||
// step from the beginning
|
// step from the beginning
|
||||||
const rerunFrom = task.can_rerun_from.length > 1 ?
|
const rerunFrom = task.can_rerun_from.length > 1 ?
|
||||||
task.can_rerun_from[1] :
|
task.can_rerun_from[1] :
|
||||||
null;
|
null;
|
||||||
|
|
||||||
addActionButton("Restart", "btn-primary", "glyphicon glyphicon-repeat", this.genRestartAction(rerunFrom), {
|
addActionButton("Restart", "btn-primary", "glyphicon glyphicon-repeat", this.genRestartAction(rerunFrom), {
|
||||||
|
@ -436,7 +441,7 @@ class TaskListItem extends React.Component {
|
||||||
const disabled = this.state.actionButtonsDisabled || !!task.pending_action;
|
const disabled = this.state.actionButtonsDisabled || !!task.pending_action;
|
||||||
|
|
||||||
actionButtons = (<div className="action-buttons">
|
actionButtons = (<div className="action-buttons">
|
||||||
{task.status === statusCodes.COMPLETED ?
|
{task.status === statusCodes.COMPLETED ?
|
||||||
<AssetDownloadButtons task={this.state.task} disabled={disabled} />
|
<AssetDownloadButtons task={this.state.task} disabled={disabled} />
|
||||||
: ""}
|
: ""}
|
||||||
{actionButtons.map(button => {
|
{actionButtons.map(button => {
|
||||||
|
@ -444,18 +449,18 @@ class TaskListItem extends React.Component {
|
||||||
const className = button.options.className || "";
|
const className = button.options.className || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={button.label} className={"inline-block " +
|
<div key={button.label} className={"inline-block " +
|
||||||
(subItems.length > 0 ? "btn-group" : "") + " " +
|
(subItems.length > 0 ? "btn-group" : "") + " " +
|
||||||
className}>
|
className}>
|
||||||
<button type="button" className={"btn btn-sm " + button.className} onClick={button.onClick} disabled={disabled}>
|
<button type="button" className={"btn btn-sm " + button.className} onClick={button.onClick} disabled={disabled}>
|
||||||
<i className={button.icon}></i>
|
<i className={button.icon}></i>
|
||||||
{button.label}
|
{button.label}
|
||||||
</button>
|
</button>
|
||||||
{subItems.length > 0 &&
|
{subItems.length > 0 &&
|
||||||
[<button key="dropdown-button"
|
[<button key="dropdown-button"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
type="button"
|
type="button"
|
||||||
className={"btn btn-sm dropdown-toggle " + button.className}
|
className={"btn btn-sm dropdown-toggle " + button.className}
|
||||||
data-toggle="dropdown"><span className="caret"></span></button>,
|
data-toggle="dropdown"><span className="caret"></span></button>,
|
||||||
<ul key="dropdown-menu" className="dropdown-menu">
|
<ul key="dropdown-menu" className="dropdown-menu">
|
||||||
{subItems.map(subItem => <li key={subItem.label}>
|
{subItems.map(subItem => <li key={subItem.label}>
|
||||||
|
@ -484,24 +489,30 @@ class TaskListItem extends React.Component {
|
||||||
: ""}
|
: ""}
|
||||||
{/* TODO: List of images? */}
|
{/* TODO: List of images? */}
|
||||||
|
|
||||||
{showOrthophotoMissingWarning ?
|
{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> : ""}
|
<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> : ""}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-8">
|
<div className="col-md-8">
|
||||||
<Console
|
<Console
|
||||||
source={this.consoleOutputUrl}
|
source={this.consoleOutputUrl}
|
||||||
refreshInterval={this.shouldRefresh() ? 3000 : undefined}
|
refreshInterval={this.shouldRefresh() ? 3000 : undefined}
|
||||||
autoscroll={true}
|
autoscroll={true}
|
||||||
height={200}
|
height={200}
|
||||||
ref={domNode => this.console = domNode}
|
ref={domNode => this.console = domNode}
|
||||||
onAddLines={this.checkForCommonErrors}
|
onAddLines={this.checkForCommonErrors}
|
||||||
/>
|
/>
|
||||||
|
<a href="javascript:void(0);" onClick={this.downloadTaskOutput} class="btn btn-sm btn-primary pull-right" title="Download task output">
|
||||||
{showMemoryErrorWarning ?
|
<i class="fa fa-download"></i>
|
||||||
|
</a>
|
||||||
|
<a href="javascript:void(0);" onClick={this.copyTaskOutput} class="btn btn-sm btn-primary pull-right" title="Copy task output">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</a>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
{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> : ""}
|
<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> : ""}
|
||||||
|
|
||||||
{showTaskWarning ?
|
{showTaskWarning ?
|
||||||
<div className="task-warning"><i className="fa fa-support"></i> <span dangerouslySetInnerHTML={{__html: this.state.friendlyTaskError}} /></div> : ""}
|
<div className="task-warning"><i className="fa fa-support"></i> <span dangerouslySetInnerHTML={{__html: this.state.friendlyTaskError}} /></div> : ""}
|
||||||
|
|
||||||
{showExitedWithCodeOneHints ?
|
{showExitedWithCodeOneHints ?
|
||||||
|
@ -510,7 +521,7 @@ class TaskListItem extends React.Component {
|
||||||
<ul>
|
<ul>
|
||||||
<li>Increase the <b>min-num-features</b> option, especially if your images have lots of vegetation</li>
|
<li>Increase the <b>min-num-features</b> option, especially if your images have lots of vegetation</li>
|
||||||
</ul>
|
</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
|
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.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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -565,12 +576,12 @@ class TaskListItem extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-1 details">
|
<div className="col-sm-1 details">
|
||||||
<i className="fa fa-image"></i> {task.images_count}
|
<i className="fa fa-image"></i> {task.images_count}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-2 details">
|
<div className="col-sm-2 details">
|
||||||
<i className="fa fa-clock-o"></i> {this.hoursMinutesSecs(this.state.time)}
|
<i className="fa fa-clock-o"></i> {this.hoursMinutesSecs(this.state.time)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-3">
|
<div className="col-sm-3">
|
||||||
{showEditLink ?
|
{showEditLink ?
|
||||||
<a href="javascript:void(0);" onClick={this.startEditing}>{statusLabel}</a>
|
<a href="javascript:void(0);" onClick={this.startEditing}>{statusLabel}</a>
|
||||||
: statusLabel}
|
: statusLabel}
|
||||||
</div>
|
</div>
|
||||||
|
|
Ładowanie…
Reference in New Issue