diff --git a/app/static/app/js/components/EditTaskPanel.jsx b/app/static/app/js/components/EditTaskPanel.jsx index 039af122..aa7fe95f 100644 --- a/app/static/app/js/components/EditTaskPanel.jsx +++ b/app/static/app/js/components/EditTaskPanel.jsx @@ -1,5 +1,6 @@ import '../css/EditTaskPanel.scss'; import React from 'react'; +import ProcessingNodeOption from './ProcessingNodeOption'; class EditTaskPanel extends React.Component { constructor(){ @@ -9,66 +10,198 @@ class EditTaskPanel extends React.Component { this.state = { name: "", - advancedOptions: false + advancedOptions: false, + loadedProcessingNodes: false, + selectedNode: null, + processingNodes: [], + editing: true }; + // Refs to ProcessingNodeOption components + this.options = {}; + this.handleNameChange = this.handleNameChange.bind(this); this.setAdvancedOptions = this.setAdvancedOptions.bind(this); + this.handleSelectNode = this.handleSelectNode.bind(this); + this.setOptionRef = this.setOptionRef.bind(this); this.save = this.save.bind(this); + this.edit = this.edit.bind(this); + } + + componentDidMount(){ + // Load projects from API + const loadProcessingNodes = () => { + function failed(){ + // Try again + setTimeout(loadProcessingNodes, 1000); + } + + this.nodesRequest = $.getJSON("/api/processingnodes/", json => { + if (Array.isArray(json)){ + + 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 + }; + }); + + // Find a node with lowest queue count + let minQueueCount = Math.min(...json.map(node => node.queue_count)); + let minQueueCountnodes = json.filter(node => node.queue_count === minQueueCount); + + // Choose at random + let autoNode = minQueueCountnodes[~~(Math.random() * minQueueCountnodes.length)]; + + nodes.unshift({ + id: autoNode.id, + key: "auto", + label: "Auto", + options: autoNode.available_options + }); + + this.setState({ + selectedNode: nodes[0], + processingNodes: nodes, + loadedProcessingNodes: true + }); + }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(); + }); + } + loadProcessingNodes(); + } + + componentWillUnmount(){ + this.nodesRequest.abort(); } handleNameChange(e){ this.setState({name: e.target.value}); } + handleSelectNode(e){ + this.options = {}; + this.setState({selectedNode: this.state.processingNodes.find(node => node.key == e.target.value)}); + } + setAdvancedOptions(flag){ return () => { this.setState({advancedOptions: flag}); }; } - save(){ + setOptionRef(optionName){ + return (component) => { + if (component) this.options[optionName] = component; + } + } + getOptions(){ + return Object.values(this.options) + .map(option => { + return { + name: option.props.name, + value: option.getValue() + }; + }) + .filter(option => option.value !== undefined); + } + + save(e){ + e.preventDefault(); + // console.log(this.getOptions()); + // console.log(this.state.selectedNode); + // console.log(this.state.name || this.namePlaceholder); + this.setState({editing: false}); + } + + edit(e){ + e.preventDefault(); + this.setState({editing: true}); } render() { - return ( -
-
-

Your images are being uploaded. In the meanwhile, check these additional options:

-
- -
- + if (this.state.editing){ + let processingNodesOptions = ""; + if (this.state.loadedProcessingNodes){ + processingNodesOptions = ( +
+
+ +
+ +
-
-
- +
+
- +
+ + +
-
-
- -
-
- - +
+
+
+ {this.state.selectedNode.options.map(option => + + )}
-
-
- + ); + }else{ + processingNodesOptions = (
+
Loading processing nodes...
+
); + } + + return ( +
+ +

Your images are being uploaded. In the meanwhile, check these additional options:

+
+ +
+ +
+ {processingNodesOptions} +
+
+ +
+
+ +
+ ); + }else{ + return ( +
+
+ +
+

Thank you! Please wait for the upload to complete.

- -
- ); + ); + } } } diff --git a/app/static/app/js/components/ProcessingNodeOption.jsx b/app/static/app/js/components/ProcessingNodeOption.jsx new file mode 100644 index 00000000..927985d8 --- /dev/null +++ b/app/static/app/js/components/ProcessingNodeOption.jsx @@ -0,0 +1,72 @@ +import React from 'react'; +import '../css/ProcessingNodeOption.scss'; +import $ from 'jquery'; + +class ProcessingNodeOption extends React.Component { + constructor(props){ + super(); + + this.state = { + value: "" + }; + + this.resetToDefault = this.resetToDefault.bind(this); + this.handleInputChange = this.handleInputChange.bind(this); + this.handleCheckboxChange = this.handleCheckboxChange.bind(this); + } + + getValue(){ + return this.state.value !== "" ? this.state.value : undefined; + } + + setTooltips(domNode){ + if (domNode !== null) $(domNode).children('[data-toggle="tooltip"]').tooltip(); + } + + resetToDefault(e){ + this.setState({value: ""}); + e.preventDefault(); + } + + handleInputChange(e){ + this.setState({value: e.target.value}); + } + + handleCheckboxChange(e){ + this.setState({value: this.state.value === "" ? true : ""}); + } + + render() { + let inputControl = ""; + if (this.props.type !== 'bool'){ + inputControl = ( + + ); + }else{ + inputControl = ( +
+ +
+ ); + } + + return ( +
+
+ {inputControl} + + +
+ ); + } +} + +export default ProcessingNodeOption; diff --git a/app/static/app/js/components/ProjectList.jsx b/app/static/app/js/components/ProjectList.jsx index 24b357e0..c6bec728 100644 --- a/app/static/app/js/components/ProjectList.jsx +++ b/app/static/app/js/components/ProjectList.jsx @@ -43,7 +43,7 @@ class ProjectList extends React.Component { render() { if (this.state.loading){ - return (
Loading projects...
); + return (
Loading projects...
); } else if (this.state.projects){ return (
    diff --git a/app/static/app/js/css/EditTaskPanel.scss b/app/static/app/js/css/EditTaskPanel.scss index 123c42d4..db80cd32 100644 --- a/app/static/app/js/css/EditTaskPanel.scss +++ b/app/static/app/js/css/EditTaskPanel.scss @@ -2,4 +2,8 @@ background-color: #ecf0f1; padding: 10px; margin-top: 10px; + + p.header{ + margin: 8px; + } } \ No newline at end of file diff --git a/app/static/app/js/css/ProcessingNodeOption.scss b/app/static/app/js/css/ProcessingNodeOption.scss new file mode 100644 index 00000000..215536eb --- /dev/null +++ b/app/static/app/js/css/ProcessingNodeOption.scss @@ -0,0 +1,14 @@ +.processing-node-option{ + .checkbox{ + margin-right: 143px; + } + + button{ + margin-left: 4px; + } + + &.form-horizontal.form-group{ + margin-left: 0; + margin-right: 0; + } +}