2017-02-09 18:06:44 +00:00
|
|
|
import '../css/EditTaskForm.scss';
|
|
|
|
import React from 'react';
|
|
|
|
import values from 'object.values';
|
|
|
|
import Utils from '../classes/Utils';
|
2017-07-24 16:39:12 +00:00
|
|
|
import EditPresetDialog from './EditPresetDialog';
|
2017-02-09 18:06:44 +00:00
|
|
|
|
|
|
|
if (!Object.values) {
|
|
|
|
values.shim();
|
|
|
|
}
|
|
|
|
|
|
|
|
class EditTaskForm extends React.Component {
|
|
|
|
static defaultProps = {
|
2017-02-10 16:29:59 +00:00
|
|
|
selectedNode: null,
|
|
|
|
task: null
|
2017-02-09 18:06:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
selectedNode: React.PropTypes.oneOfType([
|
|
|
|
React.PropTypes.string,
|
|
|
|
React.PropTypes.number
|
|
|
|
]),
|
2017-02-10 16:29:59 +00:00
|
|
|
onFormLoaded: React.PropTypes.func,
|
|
|
|
task: React.PropTypes.object
|
2017-02-09 18:06:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props){
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.namePlaceholder = "Task of " + (new Date()).toISOString();
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
error: "",
|
2017-02-10 17:28:32 +00:00
|
|
|
name: props.task !== null ? (props.task.name || "") : "",
|
2017-02-09 18:06:44 +00:00
|
|
|
loadedProcessingNodes: false,
|
2017-07-24 16:39:12 +00:00
|
|
|
loadedPresets: false,
|
|
|
|
|
2017-02-09 18:06:44 +00:00
|
|
|
selectedNode: null,
|
2017-07-21 20:48:01 +00:00
|
|
|
processingNodes: [],
|
2017-07-24 16:39:12 +00:00
|
|
|
selectedPreset: null,
|
2017-07-24 20:59:57 +00:00
|
|
|
presets: [],
|
|
|
|
|
|
|
|
editingPreset: false
|
2017-02-09 18:06:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this.handleNameChange = this.handleNameChange.bind(this);
|
|
|
|
this.handleSelectNode = this.handleSelectNode.bind(this);
|
|
|
|
this.loadProcessingNodes = this.loadProcessingNodes.bind(this);
|
2017-07-24 16:39:12 +00:00
|
|
|
this.retryLoad = this.retryLoad.bind(this);
|
2017-02-09 18:06:44 +00:00
|
|
|
this.selectNodeByKey = this.selectNodeByKey.bind(this);
|
|
|
|
this.getTaskInfo = this.getTaskInfo.bind(this);
|
2017-07-24 16:39:12 +00:00
|
|
|
this.notifyFormLoaded = this.notifyFormLoaded.bind(this);
|
|
|
|
this.loadPresets = this.loadPresets.bind(this);
|
|
|
|
this.handleSelectPreset = this.handleSelectPreset.bind(this);
|
|
|
|
this.selectPresetById = this.selectPresetById.bind(this);
|
|
|
|
this.handleEditPreset = this.handleEditPreset.bind(this);
|
2017-07-24 20:59:57 +00:00
|
|
|
this.handleCancelEditPreset = this.handleCancelEditPreset.bind(this);
|
2017-07-24 16:39:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
notifyFormLoaded(){
|
|
|
|
if (this.props.onFormLoaded &&
|
|
|
|
this.state.loadedPresets &&
|
|
|
|
this.state.loadedProcessingNodes) this.props.onFormLoaded();
|
2017-02-09 18:06:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loadProcessingNodes(){
|
|
|
|
function failed(){
|
|
|
|
// Try again
|
|
|
|
setTimeout(loadProcessingNodes, 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.nodesRequest =
|
|
|
|
$.getJSON("/api/processingnodes/?has_available_options=True", json => {
|
|
|
|
if (Array.isArray(json)){
|
|
|
|
// No nodes with options?
|
|
|
|
const noProcessingNodesError = (nodes) => {
|
|
|
|
var extra = nodes ? "We tried to reach:<ul>" + nodes.map(n => Utils.html`<li><a href="${n.url}">${n.label}</a></li>`).join("") + "</ul>" : "";
|
|
|
|
this.setState({error: `There are no usable processing nodes. ${extra}Make sure that at least one processing node is reachable and
|
|
|
|
that you have granted the current user sufficient permissions to view
|
|
|
|
the processing node (by going to Administration -- Processing Nodes -- Select Node -- Object Permissions -- Add User/Group and check CAN VIEW PROCESSING NODE).
|
|
|
|
If you are bringing a node back online, it will take about 30 seconds for WebODM to recognize it.`});
|
2017-02-15 19:20:41 +00:00
|
|
|
};
|
|
|
|
|
2017-02-09 18:06:44 +00:00
|
|
|
if (json.length === 0){
|
|
|
|
noProcessingNodesError();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let now = new Date();
|
|
|
|
|
|
|
|
let nodes = json.map(node => {
|
|
|
|
return {
|
|
|
|
id: node.id,
|
|
|
|
key: node.id,
|
|
|
|
label: `${node.hostname}:${node.port} (queue: ${node.queue_count})`,
|
|
|
|
options: node.available_options,
|
|
|
|
queue_count: node.queue_count,
|
2017-03-06 22:59:00 +00:00
|
|
|
enabled: node.online,
|
2017-02-09 18:06:44 +00:00
|
|
|
url: `http://${node.hostname}:${node.port}`
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2017-02-15 19:20:41 +00:00
|
|
|
let autoNode = null;
|
2017-02-09 18:06:44 +00:00
|
|
|
|
2017-02-15 19:25:47 +00:00
|
|
|
// If the user has selected auto, and a processing node has been assigned
|
2017-02-15 19:20:41 +00:00
|
|
|
// we need attempt to find the "auto" node to be the one that has been assigned
|
|
|
|
if (this.props.task && this.props.task.processing_node && this.props.task.auto_processing_node){
|
|
|
|
autoNode = nodes.find(node => node.id === this.props.task.processing_node);
|
2017-02-09 18:06:44 +00:00
|
|
|
}
|
|
|
|
|
2017-02-15 19:20:41 +00:00
|
|
|
if (!autoNode){
|
|
|
|
// Find a node with lowest queue count
|
|
|
|
let minQueueCount = Math.min(...nodes.filter(node => node.enabled).map(node => node.queue_count));
|
|
|
|
let minQueueCountNodes = nodes.filter(node => node.enabled && node.queue_count === minQueueCount);
|
|
|
|
|
|
|
|
if (minQueueCountNodes.length === 0){
|
|
|
|
noProcessingNodesError(nodes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Choose at random
|
|
|
|
autoNode = minQueueCountNodes[~~(Math.random() * minQueueCountNodes.length)];
|
|
|
|
}
|
2017-02-09 18:06:44 +00:00
|
|
|
|
|
|
|
nodes.unshift({
|
|
|
|
id: autoNode.id,
|
|
|
|
key: "auto",
|
|
|
|
label: "Auto",
|
|
|
|
options: autoNode.options,
|
|
|
|
enabled: true
|
|
|
|
});
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
processingNodes: nodes,
|
|
|
|
loadedProcessingNodes: true
|
|
|
|
});
|
|
|
|
|
|
|
|
// Have we specified a node?
|
2017-02-10 16:29:59 +00:00
|
|
|
if (this.props.task && this.props.task.processing_node){
|
2017-02-15 19:20:41 +00:00
|
|
|
if (this.props.task.auto_processing_node){
|
|
|
|
this.selectNodeByKey("auto");
|
|
|
|
}else{
|
|
|
|
this.selectNodeByKey(this.props.task.processing_node);
|
|
|
|
}
|
2017-02-09 18:06:44 +00:00
|
|
|
}else{
|
2017-02-15 19:20:41 +00:00
|
|
|
this.selectNodeByKey("auto");
|
2017-02-09 18:06:44 +00:00
|
|
|
}
|
|
|
|
|
2017-07-24 16:39:12 +00:00
|
|
|
this.notifyFormLoaded();
|
2017-02-09 18:06:44 +00:00
|
|
|
}else{
|
|
|
|
console.error("Got invalid json response for processing nodes", json);
|
|
|
|
failed();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.fail((jqXHR, textStatus, errorThrown) => {
|
|
|
|
// I don't expect this to fail, unless it's a development error or connection error.
|
|
|
|
// in which case we don't need to notify the user directly.
|
|
|
|
console.error("Error retrieving processing nodes", jqXHR, textStatus);
|
|
|
|
failed();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-07-24 16:39:12 +00:00
|
|
|
retryLoad(){
|
2017-02-09 18:06:44 +00:00
|
|
|
this.setState({error: ""});
|
|
|
|
this.loadProcessingNodes();
|
2017-07-24 16:39:12 +00:00
|
|
|
this.loadPresets();
|
|
|
|
}
|
|
|
|
|
|
|
|
loadPresets(){
|
|
|
|
function failed(){
|
|
|
|
// Try again
|
|
|
|
setTimeout(loadPresets, 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.presetsRequest =
|
|
|
|
$.getJSON("/api/presets/", presets => {
|
|
|
|
if (Array.isArray(presets)){
|
|
|
|
// In case somebody decides to remove all presets...
|
|
|
|
if (presets.length === 0){
|
|
|
|
this.setState({error: "There are no presets. Please create a system preset from the Administration -- Presets page, then try again."});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Choose preset
|
|
|
|
let selectedPreset = presets[0],
|
|
|
|
defaultPreset = presets.find(p => p.name === "Default"); // Do not translate Default
|
|
|
|
if (defaultPreset) selectedPreset = defaultPreset;
|
|
|
|
// TODO: look at task options
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
loadedPresets: true,
|
|
|
|
presets: presets,
|
|
|
|
selectedPreset: selectedPreset
|
|
|
|
});
|
|
|
|
this.notifyFormLoaded();
|
|
|
|
}else{
|
|
|
|
console.error("Got invalid json response for presets", json);
|
|
|
|
failed();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.fail((jqXHR, textStatus, errorThrown) => {
|
|
|
|
// I don't expect this to fail, unless it's a development error or connection error.
|
|
|
|
// in which case we don't need to notify the user directly.
|
|
|
|
console.error("Error retrieving processing nodes", jqXHR, textStatus);
|
|
|
|
failed();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
handleSelectPreset(e){
|
|
|
|
this.selectPresetById(e.target.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
selectPresetById(id){
|
|
|
|
let preset = this.state.presets.find(p => p.id === parseInt(id));
|
|
|
|
if (preset) this.setState({selectedPreset: preset});
|
2017-02-09 18:06:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount(){
|
|
|
|
this.loadProcessingNodes();
|
2017-07-24 16:39:12 +00:00
|
|
|
this.loadPresets();
|
2017-02-09 18:06:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount(){
|
2017-07-24 16:39:12 +00:00
|
|
|
if (this.nodesRequest) this.nodesRequest.abort();
|
|
|
|
if (this.presetsRequest) this.presetsRequest.abort();
|
2017-02-09 18:06:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
handleNameChange(e){
|
|
|
|
this.setState({name: e.target.value});
|
|
|
|
}
|
|
|
|
|
|
|
|
selectNodeByKey(key){
|
|
|
|
let node = this.state.processingNodes.find(node => node.key == key);
|
|
|
|
if (node) this.setState({selectedNode: node});
|
|
|
|
}
|
|
|
|
|
|
|
|
handleSelectNode(e){
|
|
|
|
this.selectNodeByKey(e.target.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
getTaskInfo(){
|
|
|
|
return {
|
|
|
|
name: this.state.name !== "" ? this.state.name : this.namePlaceholder,
|
|
|
|
selectedNode: this.state.selectedNode,
|
2017-07-24 20:59:57 +00:00
|
|
|
options: this.state.selectedPreset.options
|
2017-02-09 18:06:44 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-07-24 16:39:12 +00:00
|
|
|
handleEditPreset(){
|
2017-07-24 20:59:57 +00:00
|
|
|
this.setState({editingPreset: true});
|
|
|
|
// this.editPresetDialog.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleCancelEditPreset(){
|
|
|
|
this.setState({editingPreset: false});
|
2017-02-10 16:29:59 +00:00
|
|
|
}
|
|
|
|
|
2017-02-09 18:06:44 +00:00
|
|
|
render() {
|
|
|
|
if (this.state.error){
|
|
|
|
return (<div className="edit-task-panel">
|
|
|
|
<div className="alert alert-warning">
|
|
|
|
<div dangerouslySetInnerHTML={{__html:this.state.error}}></div>
|
2017-07-24 16:39:12 +00:00
|
|
|
<button className="btn btn-sm btn-primary" onClick={this.retryLoad}>
|
2017-02-09 18:06:44 +00:00
|
|
|
<i className="fa fa-rotate-left"></i> Retry
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>);
|
|
|
|
}
|
|
|
|
|
2017-07-24 16:39:12 +00:00
|
|
|
let taskOptions = "";
|
|
|
|
if (this.state.loadedProcessingNodes &&
|
|
|
|
this.state.selectedNode &&
|
|
|
|
this.state.loadedPresets &&
|
|
|
|
this.state.selectedPreset){
|
2017-02-10 16:29:59 +00:00
|
|
|
|
2017-07-24 16:39:12 +00:00
|
|
|
taskOptions = (
|
2017-02-09 18:06:44 +00:00
|
|
|
<div>
|
|
|
|
<div className="form-group">
|
|
|
|
<label className="col-sm-2 control-label">Processing Node</label>
|
|
|
|
<div className="col-sm-10">
|
|
|
|
<select className="form-control" value={this.state.selectedNode.key} onChange={this.handleSelectNode}>
|
|
|
|
{this.state.processingNodes.map(node =>
|
|
|
|
<option value={node.key} key={node.key} disabled={!node.enabled}>{node.label}</option>
|
|
|
|
)}
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
</div>
|
2017-07-24 16:39:12 +00:00
|
|
|
<div className="form-group form-inline">
|
2017-02-09 18:06:44 +00:00
|
|
|
<label className="col-sm-2 control-label">Options</label>
|
|
|
|
<div className="col-sm-10">
|
2017-07-24 16:39:12 +00:00
|
|
|
<select className="form-control" value={this.state.selectedPreset.id} onChange={this.handleSelectPreset}>
|
2017-07-21 20:48:01 +00:00
|
|
|
{this.state.presets.map(preset =>
|
2017-07-24 16:39:12 +00:00
|
|
|
<option value={preset.id} key={preset.id}>{preset.name}</option>
|
2017-07-21 20:48:01 +00:00
|
|
|
)}
|
2017-07-24 16:39:12 +00:00
|
|
|
</select>
|
|
|
|
<div className="btn-group presets-dropdown">
|
|
|
|
<button type="button" className="btn btn-default" onClick={this.handleEditPreset}>
|
|
|
|
<i className="fa fa-sliders"></i>
|
|
|
|
</button>
|
|
|
|
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
|
|
<span className="caret"></span>
|
|
|
|
</button>
|
|
|
|
<ul className="dropdown-menu">
|
|
|
|
<li>
|
|
|
|
<a href="javascript:void(0);" onClick={this.handleEditPreset}><i className="fa fa-sliders"></i> Edit</a>
|
|
|
|
</li>
|
|
|
|
<li className="divider"></li>
|
|
|
|
<li>
|
|
|
|
<a href="javascript:void(0);" onClick={this.handleDuplicatePreset}><i className="fa fa-copy"></i> Duplicate</a>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<a href="javascript:void(0);" onClick={this.handleDeletePreset}><i className="fa fa-trash-o"></i> Delete</a>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
2017-02-09 18:06:44 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2017-07-24 20:59:57 +00:00
|
|
|
PRESET:
|
|
|
|
{JSON.stringify(this.state.selectedPreset.options)}<br/>
|
|
|
|
|
|
|
|
|
|
|
|
{this.state.editingPreset ?
|
|
|
|
<EditPresetDialog
|
|
|
|
preset={this.state.selectedPreset}
|
|
|
|
availableOptions={this.state.selectedNode.options}
|
|
|
|
onHide={this.handleCancelEditPreset}
|
|
|
|
ref={(domNode) => { if (domNode) this.editPresetDialog = domNode; }}
|
|
|
|
/>
|
|
|
|
: ""}
|
|
|
|
|
2017-02-09 18:06:44 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}else{
|
2017-07-24 16:39:12 +00:00
|
|
|
taskOptions = (<div className="form-group">
|
|
|
|
<div className="col-sm-offset-2 col-sm-10">Loading processing nodes and presets... <i className="fa fa-refresh fa-spin fa-fw"></i></div>
|
2017-02-09 18:06:44 +00:00
|
|
|
</div>);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="edit-task-form">
|
|
|
|
<div className="form-group">
|
|
|
|
<label className="col-sm-2 control-label">Name</label>
|
|
|
|
<div className="col-sm-10">
|
2017-02-10 16:29:59 +00:00
|
|
|
<input type="text"
|
|
|
|
onChange={this.handleNameChange}
|
2017-07-24 16:39:12 +00:00
|
|
|
className="form-control"
|
2017-02-10 16:29:59 +00:00
|
|
|
placeholder={this.namePlaceholder}
|
|
|
|
value={this.state.name}
|
|
|
|
/>
|
2017-02-09 18:06:44 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2017-07-24 16:39:12 +00:00
|
|
|
{taskOptions}
|
2017-02-09 18:06:44 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default EditTaskForm;
|