Преглед изворни кода

Merge pull request #721 from smiclea/disable-exec-options

Disable execution options for bare metal replicas
Daniel Vincze пре 3 година
родитељ
комит
f04328bc5c

+ 10 - 1
src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx

@@ -31,6 +31,7 @@ import type { Schedule as ScheduleType } from '@src/@types/Schedule'
 import { ReplicaItemDetails } from '@src/@types/MainItem'
 import { ReplicaItemDetails } from '@src/@types/MainItem'
 import { MinionPool } from '@src/@types/MinionPool'
 import { MinionPool } from '@src/@types/MinionPool'
 import { ThemeProps } from '@src/components/Theme'
 import { ThemeProps } from '@src/components/Theme'
+import configLoader from '@src/utils/Config'
 
 
 const Wrapper = styled.div<any>`
 const Wrapper = styled.div<any>`
   display: flex;
   display: flex;
@@ -213,6 +214,12 @@ class ReplicaDetailsContent extends React.Component<Props, State> {
 
 
     return (
     return (
       <Schedule
       <Schedule
+        disableExecutionOptions={configLoader.config.providersDisabledExecuteOptions.some(
+          p => p
+            === this.props.endpoints.find(
+              e => e.id === this.props.item?.origin_endpoint_id,
+            )?.type,
+        )}
         schedules={this.props.scheduleStore.schedules}
         schedules={this.props.scheduleStore.schedules}
         unsavedSchedules={this.props.scheduleStore.unsavedSchedules}
         unsavedSchedules={this.props.scheduleStore.unsavedSchedules}
         adding={this.props.scheduleStore.adding}
         adding={this.props.scheduleStore.adding}
@@ -222,7 +229,9 @@ class ReplicaDetailsContent extends React.Component<Props, State> {
         onRemove={this.props.onScheduleRemove}
         onRemove={this.props.onScheduleRemove}
         onSaveSchedule={this.props.onScheduleSave}
         onSaveSchedule={this.props.onScheduleSave}
         timezone={this.state.timezone}
         timezone={this.state.timezone}
-        onTimezoneChange={timezone => { this.handleTimezoneChange(timezone) }}
+        onTimezoneChange={timezone => {
+          this.handleTimezoneChange(timezone)
+        }}
         savingIds={this.props.scheduleStore.savingIds}
         savingIds={this.props.scheduleStore.savingIds}
         enablingIds={this.props.scheduleStore.enablingIds}
         enablingIds={this.props.scheduleStore.enablingIds}
         deletingIds={this.props.scheduleStore.deletingIds}
         deletingIds={this.props.scheduleStore.deletingIds}

+ 3 - 0
src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx

@@ -53,6 +53,7 @@ const FieldInputStyled = styled(FieldInput)`
 `
 `
 type Props = {
 type Props = {
   options?: { [prop: string]: any } | null,
   options?: { [prop: string]: any } | null,
+  disableExecutionOptions: boolean,
   onChange?: (fieldName: string, fieldValue: string) => void,
   onChange?: (fieldName: string, fieldValue: string) => void,
   executionLabel: string,
   executionLabel: string,
   onCancelClick: () => void,
   onCancelClick: () => void,
@@ -117,6 +118,8 @@ class ReplicaExecutionOptions extends React.Component<Props, State> {
               value={this.getFieldValue(field)}
               value={this.getFieldValue(field)}
               label={LabelDictionary.get(field.name)}
               label={LabelDictionary.get(field.name)}
               onChange={value => this.handleValueChange(field, value)}
               onChange={value => this.handleValueChange(field, value)}
+              disabled={this.props.disableExecutionOptions}
+              description={this.props.disableExecutionOptions ? 'The execution options are disabled for the source provider' : ''}
             />
             />
           ))}
           ))}
         </Form>
         </Form>

+ 2 - 0
src/components/modules/TransferModule/Schedule/Schedule.tsx

@@ -108,6 +108,7 @@ type Props = {
   schedules: ScheduleType[] | null,
   schedules: ScheduleType[] | null,
   unsavedSchedules: ScheduleType[],
   unsavedSchedules: ScheduleType[],
   timezone: TimeZoneValue,
   timezone: TimeZoneValue,
+  disableExecutionOptions: boolean,
   onTimezoneChange: (timezone: TimeZoneValue) => void,
   onTimezoneChange: (timezone: TimeZoneValue) => void,
   onAddScheduleClick: (schedule: ScheduleType) => void,
   onAddScheduleClick: (schedule: ScheduleType) => void,
   onChange: (scheduleId: string, schedule: ScheduleType, forceSave?: boolean) => void,
   onChange: (scheduleId: string, schedule: ScheduleType, forceSave?: boolean) => void,
@@ -339,6 +340,7 @@ class Schedule extends React.Component<Props, State> {
             onRequestClose={() => { this.handleCloseOptionsModal() }}
             onRequestClose={() => { this.handleCloseOptionsModal() }}
           >
           >
             <ReplicaExecutionOptions
             <ReplicaExecutionOptions
+              disableExecutionOptions={this.props.disableExecutionOptions}
               options={this.state.executionOptions}
               options={this.state.executionOptions}
               onChange={(fieldName, value) => {
               onChange={(fieldName, value) => {
                 this.handleExecutionOptionsChange(fieldName, value)
                 this.handleExecutionOptionsChange(fieldName, value)

+ 1 - 0
src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx

@@ -697,6 +697,7 @@ class TransferItemModal extends React.Component<Props, State> {
         optionsLoadingSkipFields={[...optionsLoadingSkipFields, 'description', 'execute_now',
         optionsLoadingSkipFields={[...optionsLoadingSkipFields, 'description', 'execute_now',
           'execute_now_options', ...migrationFields.map(f => f.name)]}
           'execute_now_options', ...migrationFields.map(f => f.name)]}
         dictionaryKey={dictionaryKey}
         dictionaryKey={dictionaryKey}
+        executeNowOptionsDisabled={!providerStore.hasExecuteNowOptions(this.props.sourceEndpoint.type)}
       />
       />
     )
     )
   }
   }

+ 4 - 1
src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx

@@ -303,7 +303,10 @@ class WizardOptions extends React.Component<Props> {
         description: this.props.executeNowOptionsDisabled ? 'The \'Execute Now Options\' are disabled for the source provider' : !executeNowValue ? 'Enable \'Execute Now\' to set \'Execute Now Options\'' : `Set the options for ${this.props.wizardType} execution`,
         description: this.props.executeNowOptionsDisabled ? 'The \'Execute Now Options\' are disabled for the source provider' : !executeNowValue ? 'Enable \'Execute Now\' to set \'Execute Now Options\'' : `Set the options for ${this.props.wizardType} execution`,
       })
       })
     } else if (this.props.wizardType === 'migration' || this.props.wizardType === 'migration-destination-options-edit') {
     } else if (this.props.wizardType === 'migration' || this.props.wizardType === 'migration-destination-options-edit') {
-      fieldsSchema = [...fieldsSchema, ...migrationFields]
+      const shutdownInstanceField = migrationFields.find(f => f.name === 'shutdown_instances')!
+      shutdownInstanceField.disabled = this.props.executeNowOptionsDisabled
+      shutdownInstanceField.description = this.props.executeNowOptionsDisabled ? 'The \'Shutdown Instances\' option is disabled for the source provider' : shutdownInstanceField.description
+      fieldsSchema = [...fieldsSchema, ...migrationFields.map(f => f.name === 'shutdown_instances' ? shutdownInstanceField : f)]
     }
     }
 
 
     return fieldsSchema
     return fieldsSchema

+ 4 - 1
src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx

@@ -507,12 +507,15 @@ class WizardPageContent extends React.Component<Props, State> {
       case 'schedule':
       case 'schedule':
         body = (
         body = (
           <Schedule
           <Schedule
+            disableExecutionOptions={configLoader.config.providersDisabledExecuteOptions.some(p => p === this.props.wizardData.source?.type)}
             schedules={this.props.schedules}
             schedules={this.props.schedules}
             onAddScheduleClick={this.props.onAddScheduleClick}
             onAddScheduleClick={this.props.onAddScheduleClick}
             onChange={this.props.onScheduleChange}
             onChange={this.props.onScheduleChange}
             onRemove={this.props.onScheduleRemove}
             onRemove={this.props.onScheduleRemove}
             timezone={this.state.timezone}
             timezone={this.state.timezone}
-            onTimezoneChange={timezone => { this.handleTimezoneChange(timezone) }}
+            onTimezoneChange={timezone => {
+              this.handleTimezoneChange(timezone)
+            }}
             secondaryEmpty
             secondaryEmpty
           />
           />
         )
         )

+ 118 - 40
src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx

@@ -605,7 +605,9 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
           pageHeaderComponent={(
           pageHeaderComponent={(
             <DetailsPageHeader
             <DetailsPageHeader
               user={userStore.loggedUser}
               user={userStore.loggedUser}
-              onUserItemClick={item => { this.handleUserItemClick(item) }}
+              onUserItemClick={item => {
+                this.handleUserItemClick(item)
+              }}
             />
             />
           )}
           )}
           contentHeaderComponent={(
           contentHeaderComponent={(
@@ -625,67 +627,123 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
               item={replica}
               item={replica}
               itemId={this.replicaId}
               itemId={this.replicaId}
               instancesDetails={instanceStore.instancesDetails}
               instancesDetails={instanceStore.instancesDetails}
-              instancesDetailsLoading={instanceStore.loadingInstancesDetails || endpointStore.storageLoading || providerStore.providersLoading}
+              instancesDetailsLoading={
+                instanceStore.loadingInstancesDetails
+                || endpointStore.storageLoading
+                || providerStore.providersLoading
+              }
               endpoints={endpointStore.endpoints}
               endpoints={endpointStore.endpoints}
               storageBackends={endpointStore.storageBackends}
               storageBackends={endpointStore.storageBackends}
               scheduleStore={scheduleStore}
               scheduleStore={scheduleStore}
               networks={networkStore.networks}
               networks={networkStore.networks}
               minionPools={minionPoolStore.minionPools}
               minionPools={minionPoolStore.minionPools}
-              detailsLoading={replicaStore.replicaDetailsLoading || endpointStore.loading
-                || minionPoolStore.loadingMinionPools || this.state.initialLoading}
+              detailsLoading={
+                replicaStore.replicaDetailsLoading
+                || endpointStore.loading
+                || minionPoolStore.loadingMinionPools
+                || this.state.initialLoading
+              }
               sourceSchema={providerStore.sourceSchema}
               sourceSchema={providerStore.sourceSchema}
-              sourceSchemaLoading={providerStore.sourceSchemaLoading
-              || providerStore.sourceOptionsPrimaryLoading
-              || providerStore.sourceOptionsSecondaryLoading}
+              sourceSchemaLoading={
+                providerStore.sourceSchemaLoading
+                || providerStore.sourceOptionsPrimaryLoading
+                || providerStore.sourceOptionsSecondaryLoading
+              }
               destinationSchema={providerStore.destinationSchema}
               destinationSchema={providerStore.destinationSchema}
-              destinationSchemaLoading={providerStore.destinationSchemaLoading
-              || providerStore.destinationOptionsPrimaryLoading
-              || providerStore.destinationOptionsSecondaryLoading}
-              executionsLoading={replicaStore.startingExecution
-                || replicaStore.replicaDetailsLoading}
-              onExecutionChange={id => { this.handleExecutionChange(id) }}
+              destinationSchemaLoading={
+                providerStore.destinationSchemaLoading
+                || providerStore.destinationOptionsPrimaryLoading
+                || providerStore.destinationOptionsSecondaryLoading
+              }
+              executionsLoading={
+                replicaStore.startingExecution
+                || replicaStore.replicaDetailsLoading
+              }
+              onExecutionChange={id => {
+                this.handleExecutionChange(id)
+              }}
               executions={replicaStore.replicaDetails?.executions || []}
               executions={replicaStore.replicaDetails?.executions || []}
-              executionsTasksLoading={replicaStore.executionsTasksLoading
-                || replicaStore.replicaDetailsLoading || replicaStore.startingExecution}
+              executionsTasksLoading={
+                replicaStore.executionsTasksLoading
+                || replicaStore.replicaDetailsLoading
+                || replicaStore.startingExecution
+              }
               executionsTasks={replicaStore.executionsTasks}
               executionsTasks={replicaStore.executionsTasks}
               page={this.props.match.params.page || ''}
               page={this.props.match.params.page || ''}
-              onCancelExecutionClick={(e, f) => { this.handleCancelExecution(e, f) }}
-              onDeleteExecutionClick={execution => { this.handleDeleteExecutionClick(execution) }}
-              onExecuteClick={() => { this.handleExecuteClick() }}
-              onCreateMigrationClick={() => { this.handleCreateMigrationClick() }}
-              onDeleteReplicaClick={() => { this.handleDeleteReplicaClick() }}
-              onAddScheduleClick={schedule => { this.handleAddScheduleClick(schedule) }}
+              onCancelExecutionClick={(e, f) => {
+                this.handleCancelExecution(e, f)
+              }}
+              onDeleteExecutionClick={execution => {
+                this.handleDeleteExecutionClick(execution)
+              }}
+              onExecuteClick={() => {
+                this.handleExecuteClick()
+              }}
+              onCreateMigrationClick={() => {
+                this.handleCreateMigrationClick()
+              }}
+              onDeleteReplicaClick={() => {
+                this.handleDeleteReplicaClick()
+              }}
+              onAddScheduleClick={schedule => {
+                this.handleAddScheduleClick(schedule)
+              }}
               onScheduleChange={(scheduleId, data, forceSave) => {
               onScheduleChange={(scheduleId, data, forceSave) => {
                 this.handleScheduleChange(scheduleId, data, forceSave)
                 this.handleScheduleChange(scheduleId, data, forceSave)
               }}
               }}
-              onScheduleRemove={scheduleId => { this.handleScheduleRemove(scheduleId) }}
-              onScheduleSave={s => { this.handleScheduleSave(s) }}
+              onScheduleRemove={scheduleId => {
+                this.handleScheduleRemove(scheduleId)
+              }}
+              onScheduleSave={s => {
+                this.handleScheduleSave(s)
+              }}
             />
             />
           )}
           )}
         />
         />
         <Modal
         <Modal
           isOpen={this.state.showOptionsModal}
           isOpen={this.state.showOptionsModal}
           title="New Execution"
           title="New Execution"
-          onRequestClose={() => { this.handleCloseOptionsModal() }}
+          onRequestClose={() => {
+            this.handleCloseOptionsModal()
+          }}
         >
         >
           <ReplicaExecutionOptions
           <ReplicaExecutionOptions
-            onCancelClick={() => { this.handleCloseOptionsModal() }}
-            onExecuteClick={fields => { this.executeReplica(fields) }}
+            disableExecutionOptions={configLoader.config.providersDisabledExecuteOptions.some(
+              p => p
+                === endpointStore.endpoints.find(
+                  e => e.id === replicaStore.replicaDetails?.origin_endpoint_id,
+                )?.type,
+            )}
+            onCancelClick={() => {
+              this.handleCloseOptionsModal()
+            }}
+            onExecuteClick={fields => {
+              this.executeReplica(fields)
+            }}
           />
           />
         </Modal>
         </Modal>
         {this.state.showMigrationModal ? (
         {this.state.showMigrationModal ? (
           <Modal
           <Modal
             isOpen
             isOpen
             title="Create Migration from Replica"
             title="Create Migration from Replica"
-            onRequestClose={() => { this.handleCloseMigrationModal() }}
+            onRequestClose={() => {
+              this.handleCloseMigrationModal()
+            }}
           >
           >
             <ReplicaMigrationOptions
             <ReplicaMigrationOptions
               transferItem={this.replica}
               transferItem={this.replica}
-              minionPools={minionPoolStore.minionPools.filter(m => m.endpoint_id === this.replica?.destination_endpoint_id && m.platform === 'destination')}
+              minionPools={minionPoolStore.minionPools.filter(
+                m => m.endpoint_id === this.replica?.destination_endpoint_id
+                  && m.platform === 'destination',
+              )}
               loadingInstances={instanceStore.loadingInstancesDetails}
               loadingInstances={instanceStore.loadingInstancesDetails}
               instances={instanceStore.instancesDetails}
               instances={instanceStore.instancesDetails}
-              onCancelClick={() => { this.handleCloseMigrationModal() }}
-              onMigrateClick={opts => { this.migrateReplica(opts) }}
+              onCancelClick={() => {
+                this.handleCloseMigrationModal()
+              }}
+              onMigrateClick={opts => {
+                this.migrateReplica(opts)
+              }}
             />
             />
           </Modal>
           </Modal>
         ) : null}
         ) : null}
@@ -694,15 +752,23 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
           title="Delete Execution?"
           title="Delete Execution?"
           message="Are you sure you want to delete this execution?"
           message="Are you sure you want to delete this execution?"
           extraMessage="Deleting a Coriolis Execution is permanent!"
           extraMessage="Deleting a Coriolis Execution is permanent!"
-          onConfirmation={() => { this.handleDeleteExecutionConfirmation() }}
-          onRequestClose={() => { this.handleCloseExecutionConfirmation() }}
+          onConfirmation={() => {
+            this.handleDeleteExecutionConfirmation()
+          }}
+          onRequestClose={() => {
+            this.handleCloseExecutionConfirmation()
+          }}
         />
         />
         {this.state.showDeleteReplicaConfirmation ? (
         {this.state.showDeleteReplicaConfirmation ? (
           <DeleteReplicaModal
           <DeleteReplicaModal
             hasDisks={replicaStore.testReplicaHasDisks(this.replica)}
             hasDisks={replicaStore.testReplicaHasDisks(this.replica)}
             onRequestClose={() => this.handleCloseDeleteReplicaConfirmation()}
             onRequestClose={() => this.handleCloseDeleteReplicaConfirmation()}
-            onDeleteReplica={() => { this.handleDeleteReplicaConfirmation() }}
-            onDeleteDisks={() => { this.handleDeleteReplicaDisksConfirmation() }}
+            onDeleteReplica={() => {
+              this.handleDeleteReplicaConfirmation()
+            }}
+            onDeleteDisks={() => {
+              this.handleDeleteReplicaDisksConfirmation()
+            }}
           />
           />
         ) : null}
         ) : null}
         <AlertModal
         <AlertModal
@@ -710,16 +776,24 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
           title="Delete Replica Disks?"
           title="Delete Replica Disks?"
           message="Are you sure you want to delete this replica's disks?"
           message="Are you sure you want to delete this replica's disks?"
           extraMessage="Deleting Coriolis Replica Disks is permanent!"
           extraMessage="Deleting Coriolis Replica Disks is permanent!"
-          onConfirmation={() => { this.handleDeleteReplicaDisksConfirmation() }}
-          onRequestClose={() => { this.handleCloseDeleteReplicaDisksConfirmation() }}
+          onConfirmation={() => {
+            this.handleDeleteReplicaDisksConfirmation()
+          }}
+          onRequestClose={() => {
+            this.handleCloseDeleteReplicaDisksConfirmation()
+          }}
         />
         />
         <AlertModal
         <AlertModal
           isOpen={this.state.showCancelConfirmation}
           isOpen={this.state.showCancelConfirmation}
           title="Cancel Execution?"
           title="Cancel Execution?"
           message="Are you sure you want to cancel the current execution?"
           message="Are you sure you want to cancel the current execution?"
           extraMessage=" "
           extraMessage=" "
-          onConfirmation={() => { this.handleCancelConfirmation() }}
-          onRequestClose={() => { this.handleCloseCancelConfirmation() }}
+          onConfirmation={() => {
+            this.handleCancelConfirmation()
+          }}
+          onRequestClose={() => {
+            this.handleCloseCancelConfirmation()
+          }}
         />
         />
         <AlertModal
         <AlertModal
           isOpen={this.state.showForceCancelConfirmation}
           isOpen={this.state.showForceCancelConfirmation}
@@ -729,8 +803,12 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
 The execution is currently being cancelled.
 The execution is currently being cancelled.
 Would you like to force its cancellation?
 Would you like to force its cancellation?
 Note that this may lead to scheduled cleanup tasks being forcibly skipped, and thus manual cleanup of temporary resources on the source/destination platforms may be required.`}
 Note that this may lead to scheduled cleanup tasks being forcibly skipped, and thus manual cleanup of temporary resources on the source/destination platforms may be required.`}
-          onConfirmation={() => { this.handleCancelConfirmation(true) }}
-          onRequestClose={() => { this.handleCloseCancelConfirmation() }}
+          onConfirmation={() => {
+            this.handleCancelConfirmation(true)
+          }}
+          onRequestClose={() => {
+            this.handleCloseCancelConfirmation()
+          }}
         />
         />
         {this.renderEditReplica()}
         {this.renderEditReplica()}
       </Wrapper>
       </Wrapper>

+ 73 - 20
src/components/smart/ReplicasPage/ReplicasPage.tsx

@@ -313,6 +313,13 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
       atLeaseOneIsRunning = atLeaseOneIsRunning || status === 'RUNNING' || status === 'AWAITING_MINION_ALLOCATIONS'
       atLeaseOneIsRunning = atLeaseOneIsRunning || status === 'RUNNING' || status === 'AWAITING_MINION_ALLOCATIONS'
     })
     })
 
 
+    const replicasWithDisabledExecutionOptions = this.state.selectedReplicas
+      .filter(replica => configLoader.config.providersDisabledExecuteOptions.find(
+        p => p === endpointStore.endpoints.find(
+          e => e.id === replica.origin_endpoint_id,
+        )?.type,
+      ))
+
     const BulkActions: DropdownAction[] = [{
     const BulkActions: DropdownAction[] = [{
       label: 'Execute',
       label: 'Execute',
       action: () => { this.setState({ showExecutionOptionsModal: true }) },
       action: () => { this.setState({ showExecutionOptionsModal: true }) },
@@ -345,11 +352,19 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
               loading={replicaStore.loading}
               loading={replicaStore.loading}
               items={replicaStore.replicas}
               items={replicaStore.replicas}
               dropdownActions={BulkActions}
               dropdownActions={BulkActions}
-              onItemClick={item => { this.handleItemClick(item) }}
-              onReloadButtonClick={() => { this.handleReloadButtonClick() }}
+              onItemClick={item => {
+                this.handleItemClick(item)
+              }}
+              onReloadButtonClick={() => {
+                this.handleReloadButtonClick()
+              }}
               itemFilterFunction={(...args) => this.itemFilterFunction(...args)}
               itemFilterFunction={(...args) => this.itemFilterFunction(...args)}
-              onSelectedItemsChange={selectedReplicas => { this.setState({ selectedReplicas }) }}
-              onPaginatedItemsChange={paginatedReplicas => { this.handlePaginatedItemsChange(paginatedReplicas) }}
+              onSelectedItemsChange={selectedReplicas => {
+                this.setState({ selectedReplicas })
+              }}
+              onPaginatedItemsChange={paginatedReplicas => {
+                this.handlePaginatedItemsChange(paginatedReplicas)
+              }}
               renderItemComponent={options => (
               renderItemComponent={options => (
                 <TransferListItem
                 <TransferListItem
                   {...options}
                   {...options}
@@ -373,15 +388,23 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
               emptyListMessage="It seems like you don’t have any Replicas in this project."
               emptyListMessage="It seems like you don’t have any Replicas in this project."
               emptyListExtraMessage="The Coriolis Replica is obtained by replicating incrementally the virtual machines data from the source cloud endpoint to the target."
               emptyListExtraMessage="The Coriolis Replica is obtained by replicating incrementally the virtual machines data from the source cloud endpoint to the target."
               emptyListButtonLabel="Create a Replica"
               emptyListButtonLabel="Create a Replica"
-              onEmptyListButtonClick={() => { this.handleEmptyListButtonClick() }}
+              onEmptyListButtonClick={() => {
+                this.handleEmptyListButtonClick()
+              }}
             />
             />
           )}
           )}
           headerComponent={(
           headerComponent={(
             <PageHeader
             <PageHeader
               title="Coriolis Replicas"
               title="Coriolis Replicas"
-              onProjectChange={() => { this.handleProjectChange() }}
-              onModalOpen={() => { this.handleModalOpen() }}
-              onModalClose={() => { this.handleModalClose() }}
+              onProjectChange={() => {
+                this.handleProjectChange()
+              }}
+              onModalOpen={() => {
+                this.handleModalOpen()
+              }}
+              onModalClose={() => {
+                this.handleModalClose()
+              }}
             />
             />
           )}
           )}
         />
         />
@@ -390,8 +413,12 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
             isMultiReplicaSelection
             isMultiReplicaSelection
             hasDisks={replicaStore.replicasWithDisks.length > 0}
             hasDisks={replicaStore.replicasWithDisks.length > 0}
             loading={replicaStore.replicasWithDisksLoading}
             loading={replicaStore.replicasWithDisksLoading}
-            onRequestClose={() => { this.setState({ showDeleteReplicasModal: false }) }}
-            onDeleteReplica={() => { this.deleteSelectedReplicas() }}
+            onRequestClose={() => {
+              this.setState({ showDeleteReplicasModal: false })
+            }}
+            onDeleteReplica={() => {
+              this.deleteSelectedReplicas()
+            }}
             onDeleteDisks={() => {
             onDeleteDisks={() => {
               this.deleteReplicasDisks(replicaStore.replicasWithDisks)
               this.deleteReplicasDisks(replicaStore.replicasWithDisks)
             }}
             }}
@@ -403,19 +430,30 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
             title="Cancel Executions?"
             title="Cancel Executions?"
             message="Are you sure you want to cancel the selected replicas executions?"
             message="Are you sure you want to cancel the selected replicas executions?"
             extraMessage=" "
             extraMessage=" "
-            onConfirmation={() => { this.cancelExecutions() }}
-            onRequestClose={() => { this.setState({ showCancelExecutionModal: false }) }}
+            onConfirmation={() => {
+              this.cancelExecutions()
+            }}
+            onRequestClose={() => {
+              this.setState({ showCancelExecutionModal: false })
+            }}
           />
           />
         ) : null}
         ) : null}
         {this.state.showExecutionOptionsModal ? (
         {this.state.showExecutionOptionsModal ? (
           <Modal
           <Modal
             isOpen
             isOpen
             title="New Executions for Selected Replicas"
             title="New Executions for Selected Replicas"
-            onRequestClose={() => { this.setState({ showExecutionOptionsModal: false }) }}
+            onRequestClose={() => {
+              this.setState({ showExecutionOptionsModal: false })
+            }}
           >
           >
             <ReplicaExecutionOptions
             <ReplicaExecutionOptions
-              onCancelClick={() => { this.setState({ showExecutionOptionsModal: false }) }}
-              onExecuteClick={fields => { this.executeSelectedReplicas(fields) }}
+              disableExecutionOptions={replicasWithDisabledExecutionOptions.length === this.state.selectedReplicas.length}
+              onCancelClick={() => {
+                this.setState({ showExecutionOptionsModal: false })
+              }}
+              onExecuteClick={fields => {
+                this.executeSelectedReplicas(fields)
+              }}
             />
             />
           </Modal>
           </Modal>
         ) : null}
         ) : null}
@@ -424,7 +462,10 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
             isOpen
             isOpen
             title="Create Migrations from Selected Replicas"
             title="Create Migrations from Selected Replicas"
             onRequestClose={() => {
             onRequestClose={() => {
-              this.setState({ showCreateMigrationsModal: false, modalIsOpen: false })
+              this.setState({
+                showCreateMigrationsModal: false,
+                modalIsOpen: false,
+              })
             }}
             }}
           >
           >
             <ReplicaMigrationOptions
             <ReplicaMigrationOptions
@@ -433,9 +474,17 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
               instances={instanceStore.instancesDetails}
               instances={instanceStore.instancesDetails}
               loadingInstances={instanceStore.loadingInstancesDetails}
               loadingInstances={instanceStore.loadingInstancesDetails}
               onCancelClick={() => {
               onCancelClick={() => {
-                this.setState({ showCreateMigrationsModal: false, modalIsOpen: false })
+                this.setState({
+                  showCreateMigrationsModal: false,
+                  modalIsOpen: false,
+                })
+              }}
+              onMigrateClick={options => {
+                this.migrateSelectedReplicas(
+                  options.fields,
+                  options.uploadedUserScripts,
+                )
               }}
               }}
-              onMigrateClick={options => { this.migrateSelectedReplicas(options.fields, options.uploadedUserScripts) }}
             />
             />
           </Modal>
           </Modal>
         ) : null}
         ) : null}
@@ -445,8 +494,12 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
             title="Delete Selected Replicas Disks?"
             title="Delete Selected Replicas Disks?"
             message="Are you sure you want to delete the selected replicas' disks?"
             message="Are you sure you want to delete the selected replicas' disks?"
             extraMessage="Deleting Coriolis Replica Disks is permanent!"
             extraMessage="Deleting Coriolis Replica Disks is permanent!"
-            onConfirmation={() => { this.deleteReplicasDisks(this.state.selectedReplicas) }}
-            onRequestClose={() => { this.setState({ showDeleteDisksModal: false }) }}
+            onConfirmation={() => {
+              this.deleteReplicasDisks(this.state.selectedReplicas)
+            }}
+            onRequestClose={() => {
+              this.setState({ showDeleteDisksModal: false })
+            }}
           />
           />
         ) : null}
         ) : null}
       </Wrapper>
       </Wrapper>

+ 2 - 1
src/constants.ts

@@ -12,6 +12,7 @@ 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/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
+import { Field } from '@src/@types/Field'
 import { WizardPage } from './@types/WizardData'
 import { WizardPage } from './@types/WizardData'
 
 
 export type NavigationMenuType = {
 export type NavigationMenuType = {
@@ -65,7 +66,7 @@ export const executionOptions = [
   },
   },
 ]
 ]
 
 
-export const migrationFields = [
+export const migrationFields: Field[] = [
   {
   {
     name: 'shutdown_instances',
     name: 'shutdown_instances',
     type: 'boolean',
     type: 'boolean',