Przeglądaj źródła

Disable execution options for bare metal replicas

Previously the execution options were disabled only when creating a bare
metal replica. Now, they are disabled everywhere where execution options
are shown for it, example: scheduling, manual execution etc.
Sergiu Miclea 3 lat temu
rodzic
commit
9f8d2d4922

+ 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 { MinionPool } from '@src/@types/MinionPool'
 import { ThemeProps } from '@src/components/Theme'
+import configLoader from '@src/utils/Config'
 
 const Wrapper = styled.div<any>`
   display: flex;
@@ -213,6 +214,12 @@ class ReplicaDetailsContent extends React.Component<Props, State> {
 
     return (
       <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}
         unsavedSchedules={this.props.scheduleStore.unsavedSchedules}
         adding={this.props.scheduleStore.adding}
@@ -222,7 +229,9 @@ class ReplicaDetailsContent extends React.Component<Props, State> {
         onRemove={this.props.onScheduleRemove}
         onSaveSchedule={this.props.onScheduleSave}
         timezone={this.state.timezone}
-        onTimezoneChange={timezone => { this.handleTimezoneChange(timezone) }}
+        onTimezoneChange={timezone => {
+          this.handleTimezoneChange(timezone)
+        }}
         savingIds={this.props.scheduleStore.savingIds}
         enablingIds={this.props.scheduleStore.enablingIds}
         deletingIds={this.props.scheduleStore.deletingIds}

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

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

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

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

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

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

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

@@ -605,7 +605,9 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
           pageHeaderComponent={(
             <DetailsPageHeader
               user={userStore.loggedUser}
-              onUserItemClick={item => { this.handleUserItemClick(item) }}
+              onUserItemClick={item => {
+                this.handleUserItemClick(item)
+              }}
             />
           )}
           contentHeaderComponent={(
@@ -625,67 +627,123 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
               item={replica}
               itemId={this.replicaId}
               instancesDetails={instanceStore.instancesDetails}
-              instancesDetailsLoading={instanceStore.loadingInstancesDetails || endpointStore.storageLoading || providerStore.providersLoading}
+              instancesDetailsLoading={
+                instanceStore.loadingInstancesDetails
+                || endpointStore.storageLoading
+                || providerStore.providersLoading
+              }
               endpoints={endpointStore.endpoints}
               storageBackends={endpointStore.storageBackends}
               scheduleStore={scheduleStore}
               networks={networkStore.networks}
               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}
-              sourceSchemaLoading={providerStore.sourceSchemaLoading
-              || providerStore.sourceOptionsPrimaryLoading
-              || providerStore.sourceOptionsSecondaryLoading}
+              sourceSchemaLoading={
+                providerStore.sourceSchemaLoading
+                || providerStore.sourceOptionsPrimaryLoading
+                || providerStore.sourceOptionsSecondaryLoading
+              }
               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 || []}
-              executionsTasksLoading={replicaStore.executionsTasksLoading
-                || replicaStore.replicaDetailsLoading || replicaStore.startingExecution}
+              executionsTasksLoading={
+                replicaStore.executionsTasksLoading
+                || replicaStore.replicaDetailsLoading
+                || replicaStore.startingExecution
+              }
               executionsTasks={replicaStore.executionsTasks}
               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) => {
                 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
           isOpen={this.state.showOptionsModal}
           title="New Execution"
-          onRequestClose={() => { this.handleCloseOptionsModal() }}
+          onRequestClose={() => {
+            this.handleCloseOptionsModal()
+          }}
         >
           <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>
         {this.state.showMigrationModal ? (
           <Modal
             isOpen
             title="Create Migration from Replica"
-            onRequestClose={() => { this.handleCloseMigrationModal() }}
+            onRequestClose={() => {
+              this.handleCloseMigrationModal()
+            }}
           >
             <ReplicaMigrationOptions
               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}
               instances={instanceStore.instancesDetails}
-              onCancelClick={() => { this.handleCloseMigrationModal() }}
-              onMigrateClick={opts => { this.migrateReplica(opts) }}
+              onCancelClick={() => {
+                this.handleCloseMigrationModal()
+              }}
+              onMigrateClick={opts => {
+                this.migrateReplica(opts)
+              }}
             />
           </Modal>
         ) : null}
@@ -694,15 +752,23 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
           title="Delete Execution?"
           message="Are you sure you want to delete this execution?"
           extraMessage="Deleting a Coriolis Execution is permanent!"
-          onConfirmation={() => { this.handleDeleteExecutionConfirmation() }}
-          onRequestClose={() => { this.handleCloseExecutionConfirmation() }}
+          onConfirmation={() => {
+            this.handleDeleteExecutionConfirmation()
+          }}
+          onRequestClose={() => {
+            this.handleCloseExecutionConfirmation()
+          }}
         />
         {this.state.showDeleteReplicaConfirmation ? (
           <DeleteReplicaModal
             hasDisks={replicaStore.testReplicaHasDisks(this.replica)}
             onRequestClose={() => this.handleCloseDeleteReplicaConfirmation()}
-            onDeleteReplica={() => { this.handleDeleteReplicaConfirmation() }}
-            onDeleteDisks={() => { this.handleDeleteReplicaDisksConfirmation() }}
+            onDeleteReplica={() => {
+              this.handleDeleteReplicaConfirmation()
+            }}
+            onDeleteDisks={() => {
+              this.handleDeleteReplicaDisksConfirmation()
+            }}
           />
         ) : null}
         <AlertModal
@@ -710,16 +776,24 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
           title="Delete Replica Disks?"
           message="Are you sure you want to delete this replica's disks?"
           extraMessage="Deleting Coriolis Replica Disks is permanent!"
-          onConfirmation={() => { this.handleDeleteReplicaDisksConfirmation() }}
-          onRequestClose={() => { this.handleCloseDeleteReplicaDisksConfirmation() }}
+          onConfirmation={() => {
+            this.handleDeleteReplicaDisksConfirmation()
+          }}
+          onRequestClose={() => {
+            this.handleCloseDeleteReplicaDisksConfirmation()
+          }}
         />
         <AlertModal
           isOpen={this.state.showCancelConfirmation}
           title="Cancel Execution?"
           message="Are you sure you want to cancel the current execution?"
           extraMessage=" "
-          onConfirmation={() => { this.handleCancelConfirmation() }}
-          onRequestClose={() => { this.handleCloseCancelConfirmation() }}
+          onConfirmation={() => {
+            this.handleCancelConfirmation()
+          }}
+          onRequestClose={() => {
+            this.handleCloseCancelConfirmation()
+          }}
         />
         <AlertModal
           isOpen={this.state.showForceCancelConfirmation}
@@ -729,8 +803,12 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
 The execution is currently being cancelled.
 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.`}
-          onConfirmation={() => { this.handleCancelConfirmation(true) }}
-          onRequestClose={() => { this.handleCloseCancelConfirmation() }}
+          onConfirmation={() => {
+            this.handleCancelConfirmation(true)
+          }}
+          onRequestClose={() => {
+            this.handleCloseCancelConfirmation()
+          }}
         />
         {this.renderEditReplica()}
       </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'
     })
 
+    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[] = [{
       label: 'Execute',
       action: () => { this.setState({ showExecutionOptionsModal: true }) },
@@ -345,11 +352,19 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
               loading={replicaStore.loading}
               items={replicaStore.replicas}
               dropdownActions={BulkActions}
-              onItemClick={item => { this.handleItemClick(item) }}
-              onReloadButtonClick={() => { this.handleReloadButtonClick() }}
+              onItemClick={item => {
+                this.handleItemClick(item)
+              }}
+              onReloadButtonClick={() => {
+                this.handleReloadButtonClick()
+              }}
               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 => (
                 <TransferListItem
                   {...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."
               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"
-              onEmptyListButtonClick={() => { this.handleEmptyListButtonClick() }}
+              onEmptyListButtonClick={() => {
+                this.handleEmptyListButtonClick()
+              }}
             />
           )}
           headerComponent={(
             <PageHeader
               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
             hasDisks={replicaStore.replicasWithDisks.length > 0}
             loading={replicaStore.replicasWithDisksLoading}
-            onRequestClose={() => { this.setState({ showDeleteReplicasModal: false }) }}
-            onDeleteReplica={() => { this.deleteSelectedReplicas() }}
+            onRequestClose={() => {
+              this.setState({ showDeleteReplicasModal: false })
+            }}
+            onDeleteReplica={() => {
+              this.deleteSelectedReplicas()
+            }}
             onDeleteDisks={() => {
               this.deleteReplicasDisks(replicaStore.replicasWithDisks)
             }}
@@ -403,19 +430,30 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
             title="Cancel Executions?"
             message="Are you sure you want to cancel the selected replicas executions?"
             extraMessage=" "
-            onConfirmation={() => { this.cancelExecutions() }}
-            onRequestClose={() => { this.setState({ showCancelExecutionModal: false }) }}
+            onConfirmation={() => {
+              this.cancelExecutions()
+            }}
+            onRequestClose={() => {
+              this.setState({ showCancelExecutionModal: false })
+            }}
           />
         ) : null}
         {this.state.showExecutionOptionsModal ? (
           <Modal
             isOpen
             title="New Executions for Selected Replicas"
-            onRequestClose={() => { this.setState({ showExecutionOptionsModal: false }) }}
+            onRequestClose={() => {
+              this.setState({ showExecutionOptionsModal: false })
+            }}
           >
             <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>
         ) : null}
@@ -424,7 +462,10 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
             isOpen
             title="Create Migrations from Selected Replicas"
             onRequestClose={() => {
-              this.setState({ showCreateMigrationsModal: false, modalIsOpen: false })
+              this.setState({
+                showCreateMigrationsModal: false,
+                modalIsOpen: false,
+              })
             }}
           >
             <ReplicaMigrationOptions
@@ -433,9 +474,17 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
               instances={instanceStore.instancesDetails}
               loadingInstances={instanceStore.loadingInstancesDetails}
               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>
         ) : null}
@@ -445,8 +494,12 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
             title="Delete Selected Replicas Disks?"
             message="Are you sure you want to delete the selected replicas' disks?"
             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}
       </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/>.
 */
 
+import { Field } from '@src/@types/Field'
 import { WizardPage } from './@types/WizardData'
 
 export type NavigationMenuType = {
@@ -65,7 +66,7 @@ export const executionOptions = [
   },
 ]
 
-export const migrationFields = [
+export const migrationFields: Field[] = [
   {
     name: 'shutdown_instances',
     type: 'boolean',