Просмотр исходного кода

Added modal for replica execution options CORWEB-82

The modal is opened when executing a replica from replica detail view.
Sergiu Miclea 8 лет назад
Родитель
Сommit
2dfd579edd

+ 6 - 5
src/actions/MigrationActions/MigrationActions.js

@@ -119,7 +119,7 @@ MigrationActions.deleteReplica.listen((replica, callback = null) => {
     .catch(MigrationActions.deleteReplica.failed);
 })
 
-MigrationActions.executeReplica.listen((replica, callback = null, errorCallback = null) => {
+MigrationActions.executeReplica.listen((replica, callback = null, errorCallback = null, options = null) => {
   if (replica.type == 'replica') {
     // check if endpoints exists
     let connections = Reflux.GlobalState.connectionStore.connections
@@ -145,10 +145,11 @@ MigrationActions.executeReplica.listen((replica, callback = null, errorCallback
     }
     let projectId = Reflux.GlobalState.userStore.currentUser.project.id
 
-    let payload = {
-      execution: {
-        shutdown_instances: false
-      }
+    let payload = { execution: { shutdown_instances: false } }
+    if (options) {
+      options.forEach(o => {
+        payload.execution[o.field] = o.value || false
+      })
     }
 
     Api.sendAjaxRequest({

+ 107 - 0
src/components/ReplicaExecutionOptions/ReplicaExecutionOptions.js

@@ -0,0 +1,107 @@
+/*
+ Copyright (C) 2017  Cloudbase Solutions SRL
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+/* eslint-disable dot-notation */
+
+import React, { PropTypes } from 'react';
+import Reflux from 'reflux';
+import withStyles from 'isomorphic-style-loader/lib/withStyles';
+import s from './ReplicaExecutionOptions.scss';
+import Dropdown from '../NewDropdown';
+import Helper from "../Helper";
+
+class ReplicaExecutionOptions extends Reflux.Component {
+  static propTypes = {
+    onCancel: PropTypes.func.isRequired,
+    onExecute: PropTypes.func.isRequired
+  }
+
+  constructor(props) {
+    super(props)
+
+    this.state = {
+      fields: [
+        {
+          field: 'shutdown_instances',
+          type: 'boolean'
+        }
+      ]
+    }
+  }
+
+  componentWillMount() {
+    super.componentWillMount.call(this)
+  }
+
+  componentWillUnmount() {
+    super.componentWillMount.call(this)
+  }
+
+  fieldChange(e, field) {
+    let fields = this.state.fields.concat([]);
+    fields.find(f => f.field === field).value = e.value === 'true'
+    this.setState({ fields: fields })
+  }
+
+  render() {
+    let booleanOptions = [{ label: 'Yes', value: 'true' }, { label: 'No', value: 'false' }]
+
+    let fields = this.state.fields.map((f, i) => {
+      if (f.type !== 'boolean') {
+        return null
+      }
+
+      return (
+        <div className="form-group" key={i}>
+          <label>{Helper.convertCloudFieldLabel(f.field)}</label>
+          <Dropdown
+            options={booleanOptions}
+            onChange={(e) => { this.fieldChange(e, f.field) }}
+            placeholder="Choose a value"
+            value={f.value ? booleanOptions[0] : booleanOptions[1]}
+          />
+        </div>
+      )
+    })
+    let modalBody = (
+      <div className={s.container}>
+        <div className={s.formContainer}>
+          {fields}
+        </div>
+        <div className={s.buttons}>
+          <button className="gray" onClick={() => this.props.onCancel()}>Cancel</button>
+          <button onClick={() => this.props.onExecute(this.state.fields)}>Execute</button>
+        </div>
+      </div>
+    )
+
+    return (
+      <div className={s.root}>
+        <div className={s.header}>
+          <h3>Replica Execution Options</h3>
+        </div>
+        <div className={s.images}>
+          <div className={s.replicaImage} />
+          <div className={s.arrowImage} />
+          <div className={s.executionImage} />
+        </div>
+        {modalBody}
+      </div>
+    )
+  }
+}
+
+export default withStyles(ReplicaExecutionOptions, s)

Разница между файлами не показана из-за своего большого размера
+ 79 - 0
src/components/ReplicaExecutionOptions/ReplicaExecutionOptions.scss


+ 6 - 0
src/components/ReplicaExecutionOptions/package.json

@@ -0,0 +1,6 @@
+{
+    "name": "ReplicaExecutionOptions",
+    "version": "0.0.0",
+    "private": true,
+    "main": "./ReplicaExecutionOptions.js"
+}

+ 48 - 3
src/components/ReplicaExecutions/ReplicaExecutions.js

@@ -16,6 +16,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 /* eslint-disable no-trailing-spaces */
 import React, { Component, PropTypes } from 'react';
+import Modal from 'react-modal'
 import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import s from './ReplicaExecutions.scss';
 import Helper from '../Helper';
@@ -27,6 +28,7 @@ import Tasks from '../Tasks';
 import ExecutionsTimeline from '../ExecutionsTimeline';
 import { tasksPollTimeout } from '../../config'
 import ConfirmationDialog from '../ConfirmationDialog'
+import ReplicaExecutionOptions from '../ReplicaExecutionOptions'
 
 
 const title = 'Replica Executions';
@@ -48,6 +50,7 @@ class ReplicaExecutions extends Component {
     this.state = {
       executionRef: null,
       tasks: null,
+      showExecutionModal: false,
       deleteConfirmationDialog: {
         visible: false,
         message: "Are you sure?",
@@ -117,8 +120,17 @@ class ReplicaExecutions extends Component {
     })
   }
 
-  executeNow() {
-    MigrationActions.executeReplica(this.props.replica)
+  showExecutionModal() {
+    this.setState({ showExecutionModal: true })
+  }
+
+  closeExecutionModal() {
+    this.setState({ showExecutionModal: false })
+  }
+
+  executeNow(options) {
+    this.closeExecutionModal()
+    MigrationActions.executeReplica(this.props.replica, null, null, options)
     clearInterval(this.timeout)
     this.timeout = setInterval((e) => this.pollTasks(e), tasksPollTimeout)
   }
@@ -194,6 +206,28 @@ class ReplicaExecutions extends Component {
   }
 
   render() {
+    let modalStyle = {
+      overlay: {
+        position: "fixed",
+        top: 0,
+        left: 0,
+        right: 0,
+        bottom: 0,
+        backgroundColor: "rgba(164, 170, 181, 0.69)"
+      },
+      content: {
+        padding: "0px",
+        borderRadius: "4px",
+        border: "none",
+        bottom: "auto",
+        width: "576px",
+        height: "auto",
+        left: "50%",
+        top: "120px",
+        marginLeft: "-288px"
+      }
+    }
+
     if (this.props.replica) {
       if (this.props.replica.executions.length && this.state.executionRef) {
         let executionBtn = <button className="wire red" onClick={(e) => this.deleteExecution(e)}>Delete</button>
@@ -259,9 +293,20 @@ class ReplicaExecutions extends Component {
                 <span className="icon"></span>
                 <h3>It looks like there are no executions in this replica</h3>
                 <p>This replica has not been executed yet</p>
-                <button onClick={(e) => this.executeNow(e)}>Execute Now</button>
+                <button onClick={this.showExecutionModal.bind(this)}>Execute Now</button>
               </div>
             </div>
+            <Modal
+              isOpen={this.state.showExecutionModal}
+              style={modalStyle}
+              onRequestClose={this.closeExecutionModal.bind(this)}
+              contentLabel="Replica Execution Options"
+            >
+              <ReplicaExecutionOptions
+                onCancel={this.closeExecutionModal.bind(this)}
+                onExecute={this.executeNow.bind(this)}
+              />
+            </Modal>
           </div>
         )
       }

+ 48 - 5
src/components/ReplicaView/ReplicaView.js

@@ -17,6 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React, { PropTypes } from 'react';
 import Reflux from 'reflux';
+import Modal from 'react-modal'
 import withStyles from 'isomorphic-style-loader/lib/withStyles';
 import s from './ReplicaView.scss';
 import Header from '../Header';
@@ -28,6 +29,7 @@ import TextTruncate from 'react-text-truncate';
 import Location from '../../core/Location';
 import ConfirmationDialog from '../ConfirmationDialog'
 import { tasksPollTimeout } from '../../config'
+import ReplicaExecutionOptions from '../ReplicaExecutionOptions'
 
 class ReplicaView extends Reflux.Component {
 
@@ -46,6 +48,7 @@ class ReplicaView extends Reflux.Component {
     this.state = {
       title: 'Coriolis: View Replica',
       isBeingExecuted: false,
+      showExecutionModal: false,
       confirmationDialog: {
         visible: false,
         message: "Are you sure?",
@@ -84,8 +87,16 @@ class ReplicaView extends Reflux.Component {
       })
   }
 
-  executeReplica() {
-    this.setState({ isBeingExecuted: true })
+  showExecutionModal() {
+    this.setState({ showExecutionModal: true })
+  }
+
+  closeExecutionModal() {
+    this.setState({ showExecutionModal: false })
+  }
+
+  executeReplica(options) {
+    this.setState({ isBeingExecuted: true, showExecutionModal: false })
     let item = this.state.replicas.filter(replica => replica.id == this.props.replicaId)[0]
     MigrationActions.executeReplica(item, () => {
       this.pollReplicaExecution()
@@ -93,7 +104,7 @@ class ReplicaView extends Reflux.Component {
     }, () => {
       this.pollReplicaExecution()
       this.setState({ isBeingExecuted: false })
-    })
+    }, options)
   }
 
   goBack() {
@@ -108,7 +119,7 @@ class ReplicaView extends Reflux.Component {
         Location.push('/cloud-endpoints')
         break
       case "start":
-        MigrationActions.executeReplica(item)
+        this.showExecutionModal()
         break
       default:
         break
@@ -124,6 +135,27 @@ class ReplicaView extends Reflux.Component {
   }
 
   render() {
+    let modalStyle = {
+      overlay: {
+        position: "fixed",
+        top: 0,
+        left: 0,
+        right: 0,
+        bottom: 0,
+        backgroundColor: "rgba(164, 170, 181, 0.69)"
+      },
+      content: {
+        padding: "0px",
+        borderRadius: "4px",
+        border: "none",
+        bottom: "auto",
+        width: "576px",
+        height: "auto",
+        left: "50%",
+        top: "120px",
+        marginLeft: "-288px"
+      }
+    }
     let item = this.currentReplica(this.props.replicaId)
     let title = "Edit"
 
@@ -156,7 +188,7 @@ class ReplicaView extends Reflux.Component {
                   <button
                     className="gray"
                     disabled={item.status === "RUNNING" || this.state.isBeingExecuted}
-                    onClick={(e) => this.executeReplica(e)}
+                    onClick={this.showExecutionModal.bind(this)}
                   >
                     Execute Now
                   </button>
@@ -192,6 +224,17 @@ class ReplicaView extends Reflux.Component {
             onConfirm={(e) => this.state.confirmationDialog.onConfirm(e)}
             onCancel={(e) => this.state.confirmationDialog.onCancel(e)}
           />
+          <Modal
+            isOpen={this.state.showExecutionModal}
+            style={modalStyle}
+            onRequestClose={this.closeExecutionModal.bind(this)}
+            contentLabel="Replica Execution Options"
+          >
+            <ReplicaExecutionOptions
+              onCancel={this.closeExecutionModal.bind(this)}
+              onExecute={this.executeReplica.bind(this)}
+            />
+          </Modal>
         </div>
       )
     } else {

+ 2 - 1
src/constants/CloudLabels.js

@@ -74,7 +74,8 @@ export const defaultLabels = {
   session_token: "Session Token",
   clone_disks: "Clone Disks",
   force: "Force",
-  skip_os_morphing: "Skip OS Morphing"
+  skip_os_morphing: "Skip OS Morphing",
+  shutdown_instances: "Shutdown Instances"
 }
 
 export const cloudLabels = {

Некоторые файлы не были показаны из-за большого количества измененных файлов