Bläddra i källkod

Merge pull request #627 from smiclea/oci-minion-pool

Add OCI provider minion pool support CORWEB-258
Nashwan Azhari 5 år sedan
förälder
incheckning
681067dcef

+ 50 - 25
src/components/organisms/MinionEndpointModal/MinionEndpointModal.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
+import { CSSTransitionGroup } from 'react-transition-group'
 
 import Modal from '../../molecules/Modal'
 import { Providers, ProviderTypes } from '../../../@types/Providers'
@@ -37,6 +38,15 @@ const LoadingWrapper = styled.div`
 `
 const ContentWrapper = styled.div`
   padding: 48px;
+  > span {
+    display: flex;
+    justify-content: center;
+    margin-left: -24px;
+    transition: all 250ms ease-out;
+    > div {
+      margin-left: 24px;
+    }
+  }
 `
 const NoEndpoints = styled.div`
   padding: 64px;
@@ -54,6 +64,20 @@ const ProviderWrapper = styled.div`
   align-items: center;
   justify-content: center;
   flex-direction: column;
+  &.providers-group-transition-leave {
+    opacity: 1;
+  }
+  &.providers-group-transition-leave-active {
+    opacity: 0.01;
+    transition: opacity 250ms ease-out;
+  }
+  &.providers-group-transition-enter {
+    opacity: 0.01;
+  }
+  &.providers-group-transition-enter-active {
+    opacity: 1;
+    transition: opacity 250ms ease-out;
+  }
 `
 const ButtonWrapper = styled.div`
   display: flex;
@@ -112,27 +136,6 @@ class MinionEndpointModal extends React.Component<Props, State> {
     )
   }
 
-  renderProvider(providerName: ProviderTypes, providerEndpoints: Endpoint[]) {
-    return (
-      <ProviderWrapper key={providerName}>
-        <EndpointLogos
-          height={128}
-          endpoint={providerName}
-          style={{ marginBottom: '16px' }}
-        />
-        <Dropdown
-          items={providerEndpoints}
-          valueField="id"
-          labelField="name"
-          noSelectionMessage="Choose an endpoint"
-          centered
-          selectedItem={this.state.selectedEndpoint}
-          onChange={endpoint => { this.setState({ selectedEndpoint: endpoint }) }}
-        />
-      </ProviderWrapper>
-    )
-  }
-
   renderPoolPlatform() {
     return (
       <PoolPlatformWrapper>
@@ -178,10 +181,32 @@ class MinionEndpointModal extends React.Component<Props, State> {
 
     return (
       <ContentWrapper>
-        {availableProviders.map(providerName => this.renderProvider(
-          providerName as any,
-          this.props.endpoints.filter(e => e.type === providerName),
-        ))}
+        <CSSTransitionGroup
+          transitionName="providers-group-transition"
+          transitionLeave
+          transitionEnter
+          transitionLeaveTimeout={250}
+          transitionEnterTimeout={250}
+        >
+          {availableProviders.map(providerName => (
+            <ProviderWrapper key={providerName}>
+              <EndpointLogos
+                height={128}
+                endpoint={providerName}
+                style={{ marginBottom: '16px' }}
+              />
+              <Dropdown
+                items={this.props.endpoints.filter(e => e.type === providerName)}
+                valueField="id"
+                labelField="name"
+                noSelectionMessage="Choose an endpoint"
+                centered
+                selectedItem={this.state.selectedEndpoint?.type === providerName ? this.state.selectedEndpoint : null}
+                onChange={endpoint => { this.setState({ selectedEndpoint: endpoint }) }}
+              />
+            </ProviderWrapper>
+          ))}
+        </CSSTransitionGroup>
       </ContentWrapper>
     )
   }

+ 91 - 44
src/components/organisms/MinionPoolModal/MinionPoolModal.tsx

@@ -25,13 +25,14 @@ import type { Endpoint as EndpointType } from '../../../@types/Endpoint'
 import type { Field } from '../../../@types/Field'
 import ObjectUtils from '../../../utils/ObjectUtils'
 import KeyboardManager from '../../../utils/KeyboardManager'
-import { MinionPool } from '../../../@types/MinionPool'
 import MinionPoolModalContent from './MinionPoolModalContent'
 import minionPoolStore from '../../../stores/MinionPoolStore'
 
 import minionPoolImage from './images/minion-pool.svg'
 import StyleProps from '../../styleUtils/StyleProps'
 import notificationStore from '../../../stores/NotificationStore'
+import providerStore, { getFieldChangeOptions } from '../../../stores/ProviderStore'
+import { MinionPool } from '../../../@types/MinionPool'
 
 const Wrapper = styled.div<any>`
   padding: 24px 0 32px 0;
@@ -71,7 +72,8 @@ const Buttons = styled.div<any>`
 type Props = {
   cancelButtonText: string,
   endpoint: EndpointType,
-  minionPool?: MinionPool | null
+  minionPool?: MinionPool | null,
+  editableData?: any | null
   platform: 'source' | 'destination',
   onCancelClick: () => void,
   onResizeUpdate?: (scrollableRef: HTMLElement, scrollOffset?: number) => void,
@@ -80,7 +82,7 @@ type Props = {
 }
 type State = {
   invalidFields: any[],
-  minionPool: any | null
+  editableData: any | null
   saving: boolean
 }
 @observer
@@ -91,7 +93,7 @@ class MinionPoolModal extends React.Component<Props, State> {
 
   state: State = {
     invalidFields: [],
-    minionPool: null,
+    editableData: null,
     saving: false,
   }
 
@@ -112,11 +114,19 @@ class MinionPoolModal extends React.Component<Props, State> {
         return
       }
       await minionPoolStore.loadMinionPoolSchema(this.props.endpoint.type, this.props.platform)
-      await minionPoolStore.loadEnvOptions(
-        this.props.endpoint.id,
-        this.props.endpoint.type,
-        this.props.platform,
-      )
+
+      await providerStore.loadProviders()
+      const providers = providerStore.providers
+      if (!providers) {
+        return
+      }
+      await minionPoolStore.loadOptions({
+        providers,
+        optionsType: this.props.platform,
+        endpoint: this.props.endpoint,
+        envData: this.envData,
+        useCache: true,
+      })
 
       this.fillRequiredDefaults()
     }
@@ -127,19 +137,19 @@ class MinionPoolModal extends React.Component<Props, State> {
   }
 
   UNSAFE_componentWillReceiveProps(props: Props) {
-    if (props.minionPool) {
+    if (props.editableData) {
       this.setState(prevState => ({
-        minionPool: {
-          ...prevState.minionPool,
-          ...ObjectUtils.flatten(props.minionPool || {}),
+        editableData: {
+          ...prevState.editableData,
+          ...ObjectUtils.flatten(props.editableData || {}),
         },
       }))
     }
 
     if (props.platform) {
       this.setState(prevState => ({
-        minionPool: {
-          ...prevState.minionPool,
+        editableData: {
+          ...prevState.editableData,
           platform: props.platform,
         },
       }))
@@ -154,16 +164,29 @@ class MinionPoolModal extends React.Component<Props, State> {
   }
 
   get isLoading() {
-    return minionPoolStore.loadingMinionPoolSchema || minionPoolStore.loadingMinionPools
-      || minionPoolStore.loadingEnvOptions
+    return minionPoolStore.loadingMinionPoolSchema
+      || minionPoolStore.loadingMinionPools
+      || minionPoolStore.optionsPrimaryLoading
+      || providerStore.providersLoading
+  }
+
+  get envData() {
+    let envData: any = null
+    Object.keys(this.state.editableData).forEach(prop => {
+      if (!minionPoolStore.minionPoolDefaultSchema.find(f => f.name === prop)) {
+        envData = envData || {}
+        envData[prop] = this.state.editableData[prop]
+      }
+    })
+    return envData
   }
 
   getFieldValue(field?: Field | null) {
-    if (!field || !this.state.minionPool) {
+    if (!field || !this.state.editableData) {
       return ''
     }
-    if (this.state.minionPool[field.name] != null) {
-      return this.state.minionPool[field.name]
+    if (this.state.editableData[field.name] != null) {
+      return this.state.editableData[field.name]
     }
 
     if (Object.keys(field).find(k => k === 'default')) {
@@ -204,39 +227,39 @@ class MinionPoolModal extends React.Component<Props, State> {
     }
     this.setState({ saving: true })
     try {
-      if (this.state.minionPool?.id) {
+      if (this.props.minionPool?.id) {
         await this.update()
       } else {
         await this.add()
       }
     } catch (err) {
+      console.error(err)
       this.setState({ saving: false })
     }
   }
 
   async update() {
-    const stateMinionPool = { ...this.state.minionPool }
-    const minionPool = minionPoolStore.minionPools.find(e => e.id === stateMinionPool.id)
-    if (!minionPool) {
-      throw new Error('Minion pool not found!')
+    const stateMinionPool = {
+      ...this.state.editableData,
+      id: this.props.minionPool?.id,
     }
     delete stateMinionPool.platform
     delete stateMinionPool.endpoint_id
-    await minionPoolStore.update(stateMinionPool)
+    await minionPoolStore.update(this.props.endpoint.type, stateMinionPool)
     if (this.props.onUpdateComplete) {
-      this.props.onUpdateComplete(`/minion-pools/${stateMinionPool.id}`)
+      this.props.onUpdateComplete(`/minion-pools/${this.props.minionPool?.id}`)
     }
   }
 
   async add() {
-    await minionPoolStore.add(this.props.endpoint.id, this.state.minionPool)
+    await minionPoolStore.add(this.props.endpoint.type, this.props.endpoint.id, this.state.editableData)
     notificationStore.alert('Minion Pool created', 'success')
     this.props.onRequestClose()
   }
 
   fillRequiredDefaults() {
     this.setState(prevState => {
-      const minionPool: any = { ...prevState.minionPool }
+      const minionPool: any = { ...prevState.editableData }
       const requiredFieldsDefaults = minionPoolStore.minionPoolCombinedSchema
         .filter(f => f.required && f.default != null)
       requiredFieldsDefaults.forEach(f => {
@@ -244,26 +267,48 @@ class MinionPoolModal extends React.Component<Props, State> {
           minionPool[f.name] = f.default
         }
       })
-      return { minionPool }
+      return { editableData: minionPool }
     })
   }
 
-  handleFieldsChange(items: { field: Field, value: any }[]) {
+  async loadExtraOptions(field: Field | null, type: 'source' | 'destination', useCache: boolean = true) {
+    const envData = getFieldChangeOptions({
+      providerName: this.props.endpoint.type,
+      schema: minionPoolStore.minionPoolEnvSchema,
+      data: this.envData,
+      field,
+      type,
+    })
+    if (!envData) {
+      return
+    }
+    await minionPoolStore.loadOptions({
+      providers: providerStore.providers!,
+      optionsType: type,
+      endpoint: this.props.endpoint,
+      envData,
+      useCache,
+    })
+    this.fillRequiredDefaults()
+  }
+
+  handleFieldChange(field: Field, value: any) {
     this.setState(prevState => {
-      const minionPool: any = { ...prevState.minionPool }
-
-      items.forEach(item => {
-        let value = item.value
-        if (item.field.type === 'array') {
-          const arrayItems = minionPool[item.field.name] || []
-          value = arrayItems.find((v: any) => v === item.value)
-            ? arrayItems.filter((v: any) => v !== item.value) : [...arrayItems, item.value]
-        }
+      const minionPool: any = { ...prevState.editableData }
 
-        minionPool[item.field.name] = value
-      })
+      if (field.type === 'array') {
+        const arrayItems = minionPool[field.name] || []
+        value = arrayItems.find((v: any) => v === value)
+          ? arrayItems.filter((v: any) => v !== value) : [...arrayItems, value]
+      }
 
-      return { minionPool }
+      minionPool[field.name] = value
+
+      return { editableData: minionPool }
+    }, () => {
+      if (field.type !== 'string' || field.enum) {
+        this.loadExtraOptions(field, this.props.platform, true)
+      }
     })
   }
 
@@ -305,6 +350,8 @@ class MinionPoolModal extends React.Component<Props, State> {
         <MinionPoolModalContent
           endpoint={this.props.endpoint}
           platform={this.props.platform}
+          optionsLoading={minionPoolStore.optionsSecondaryLoading}
+          optionsLoadingSkipFields={minionPoolStore.minionPoolDefaultSchema.map(f => f.name)}
           envOptionsDisabled={this.props.minionPool != null && this.props.minionPool.status !== 'DEALLOCATED'}
           defaultSchema={minionPoolStore.minionPoolDefaultSchema}
           envSchema={minionPoolStore.minionPoolEnvSchema}
@@ -314,7 +361,7 @@ class MinionPoolModal extends React.Component<Props, State> {
           getFieldValue={field => this.getFieldValue(field)}
           onFieldChange={(field, value) => {
             if (field) {
-              this.handleFieldsChange([{ field, value }])
+              this.handleFieldChange(field, value)
             }
           }}
           onCreateClick={() => { this.create() }}

+ 3 - 0
src/components/organisms/MinionPoolModal/MinionPoolModalContent.tsx

@@ -124,6 +124,8 @@ type Props = {
   invalidFields: string[],
   endpoint: Endpoint
   platform: 'source' | 'destination'
+  optionsLoading: boolean
+  optionsLoadingSkipFields: string[],
   getFieldValue: (field: Field | null | undefined) => any,
   onFieldChange: (field: Field | null, value: any) => void,
   disabled: boolean,
@@ -216,6 +218,7 @@ class MinionPoolModalContent extends React.Component<Props, State> {
             value={this.props.getFieldValue(field)}
             onChange={value => { this.props.onFieldChange(field, value) }}
             nullableBoolean={field.nullableBoolean != null ? field.nullableBoolean : true}
+            disabledLoading={this.props.optionsLoading && !this.props.optionsLoadingSkipFields.find(fn => fn === field.name)}
           />
         )
       }

+ 36 - 7
src/components/pages/MinionPoolDetailsPage/MinionPoolDetailsPage.tsx

@@ -38,6 +38,8 @@ import MinionPoolDetailsContent from '../../organisms/MinionPoolDetailsContent/M
 import replicaStore from '../../../stores/ReplicaStore'
 import migrationStore from '../../../stores/MigrationStore'
 import MinionPoolConfirmationModal from '../../molecules/MinionPoolConfirmationModal/MinionPoolConfirmationModal'
+import providerStore from '../../../stores/ProviderStore'
+import { Field } from '../../../@types/Field'
 
 const Wrapper = styled.div<any>``
 
@@ -93,6 +95,28 @@ class MinionPoolDetailsPage extends React.Component<Props, State> {
     return minionPoolStore.minionPoolDetails
   }
 
+  get envData() {
+    return this.getSchemaData(minionPoolStore.minionPoolEnvSchema, minionPoolStore.minionPoolDetails?.environment_options)
+  }
+
+  get editableData() {
+    const envData = this.envData
+    const defaultData = this.getSchemaData(minionPoolStore.minionPoolDefaultSchema, minionPoolStore.minionPoolDetails)
+    return defaultData || envData ? { ...defaultData, ...envData } : null
+  }
+
+  getSchemaData(schema: Field[], data: any | null) {
+    let schemaData: any = null
+    const details: any = data || {}
+    Object.keys(details).forEach(prop => {
+      if (schema.find(f => f.name === prop)) {
+        schemaData = schemaData || {}
+        schemaData[prop] = details[prop]
+      }
+    })
+    return schemaData
+  }
+
   getStatus() {
     return this.minionPool?.status
   }
@@ -121,12 +145,14 @@ class MinionPoolDetailsPage extends React.Component<Props, State> {
       endpoint.type,
       minionPool.platform,
     )
-    await minionPoolStore.loadEnvOptions(
-      endpoint.id,
-      endpoint.type,
-      minionPool.platform,
-      { useCache: true },
-    )
+    await providerStore.loadProviders()
+    await minionPoolStore.loadOptions({
+      providers: providerStore.providers!,
+      endpoint,
+      optionsType: minionPool.platform,
+      useCache: true,
+      envData: this.envData,
+    })
   }
 
   handleUserItemClick(item: { value: string }) {
@@ -241,6 +267,7 @@ class MinionPoolDetailsPage extends React.Component<Props, State> {
           endpoint={endpoint}
           onCancelClick={() => { this.closeEditModal() }}
           onRequestClose={() => { this.closeEditModal() }}
+          editableData={this.editableData}
           minionPool={this.minionPool}
           platform={this.minionPool?.platform || 'source'}
           onUpdateComplete={r => { this.handleUpdateComplete(r) }}
@@ -329,7 +356,9 @@ class MinionPoolDetailsPage extends React.Component<Props, State> {
               endpoints={endpointStore.endpoints}
               schema={minionPoolStore.minionPoolCombinedSchema}
               schemaLoading={minionPoolStore.loadingMinionPoolSchema
-                || minionPoolStore.loadingEnvOptions}
+                || minionPoolStore.optionsPrimaryLoading
+                || providerStore.providersLoading
+                || minionPoolStore.optionsSecondaryLoading}
               page={this.props.match.params.page || ''}
               loading={minionPoolStore.loadingMinionPoolDetails}
               onDeleteMinionPoolClick={() => { this.handleDeleteMinionPoolClick() }}

+ 30 - 0
src/plugins/endpoint/default/MinionPoolSchemaPlugin.ts

@@ -0,0 +1,30 @@
+import { Field } from '../../../@types/Field'
+import DomUtils from '../../../utils/DomUtils'
+
+export default class MinionPoolSchemaPlugin {
+  static getMinionPoolToOptionsQuery(envData?: any) {
+    let envString = ''
+    if (envData) {
+      envString = `?env=${DomUtils.encodeToBase64Url(envData)}`
+    }
+    return envString
+  }
+
+  static minionPoolTransformOptionsFields(fields: Field[]) {
+    return fields
+  }
+
+  static getMinionPoolEnv(schema: Field[], data: any) {
+    const payload: any = {}
+    schema.forEach(field => {
+      if (data[field.name] === null || data[field.name] === undefined || data[field.name] === '') {
+        if (field.default !== null) {
+          payload[field.name] = field.default
+        }
+      } else {
+        payload[field.name] = data[field.name]
+      }
+    })
+    return payload
+  }
+}

+ 16 - 0
src/plugins/endpoint/index.ts

@@ -31,6 +31,9 @@ import DefaultInstanceInfoPlugin from './default/InstanceInfoPlugin'
 import OciInstanceInfoPlugin from './oci/InstanceInfoPlugin'
 import { ProviderTypes } from '../../@types/Providers'
 
+import DefaultMinionPoolSchemaPlugin from './default/MinionPoolSchemaPlugin'
+import OpenstackMinionPoolSchemaPlugin from './openstack/MinionPoolSchemaPlugin'
+
 const hasKey = <O>(obj: O, key: keyof any): key is keyof O => key in obj
 
 export const ConnectionSchemaPlugin = {
@@ -90,3 +93,16 @@ export const InstanceInfoPlugin = {
     return map.default
   },
 }
+
+export const MinionPoolSchemaPlugin = {
+  for: (provider: ProviderTypes) => {
+    const map = {
+      default: DefaultMinionPoolSchemaPlugin,
+      openstack: OpenstackMinionPoolSchemaPlugin,
+    }
+    if (hasKey(map, provider)) {
+      return map[provider]
+    }
+    return map.default
+  },
+}

+ 20 - 0
src/plugins/endpoint/openstack/MinionPoolSchemaPlugin.ts

@@ -0,0 +1,20 @@
+import DefaultMinionPoolSchemaPlugin from '../default/MinionPoolSchemaPlugin'
+import { Field } from '../../../@types/Field'
+import DomUtils from '../../../utils/DomUtils'
+
+export default class MinionPoolSchemaPlugin {
+  static getMinionPoolToOptionsQuery(envData: any) {
+    return `?env=${DomUtils.encodeToBase64Url({ ...envData, list_all_destination_networks: true })}`
+  }
+
+  static minionPoolTransformOptionsFields(fields: Field[]) {
+    // Remove this field, as all networks are always listed
+    fields = fields.filter(f => f.name !== 'list_all_destination_networks')
+    return fields
+  }
+
+  static getMinionPoolEnv(schema: Field[], data: any) {
+    const payload: any = DefaultMinionPoolSchemaPlugin.getMinionPoolEnv(schema, data)
+    return { ...payload, list_all_destination_networks: true }
+  }
+}

+ 42 - 35
src/sources/MinionPoolSource.ts

@@ -13,6 +13,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 import Api from '../utils/ApiCaller'
+import DefaultMinionPoolSchemaPlugin from '../plugins/endpoint/default/MinionPoolSchemaPlugin'
 
 import configLoader from '../utils/Config'
 import { MinionPool, MinionPoolDetails } from '../@types/MinionPool'
@@ -20,24 +21,9 @@ import { ProviderTypes } from '../@types/Providers'
 import { Field } from '../@types/Field'
 import { providerTypes } from '../constants'
 import { SchemaParser } from './Schemas'
-import { OptionValues } from '../@types/Endpoint'
+import { Endpoint, OptionValues } from '../@types/Endpoint'
 import { MinionPoolAction } from '../stores/MinionPoolStore'
 import { Execution } from '../@types/Execution'
-import DomUtils from '../utils/DomUtils'
-
-const transformFieldsToPayload = (schema: Field[], data: any) => {
-  const payload: any = {}
-  schema.forEach(field => {
-    if (data[field.name] === null || data[field.name] === undefined || data[field.name] === '') {
-      if (field.default !== null) {
-        payload[field.name] = field.default
-      }
-    } else {
-      payload[field.name] = data[field.name]
-    }
-  })
-  return payload
-}
 
 class MinionPoolSource {
   getMinionPoolDefaultSchema(): Field[] {
@@ -141,13 +127,25 @@ class MinionPoolSource {
     return response.data.minion_pool
   }
 
-  async loadEnvOptions(endpointId: string, platform: 'source' | 'destination', useCache?: boolean): Promise<OptionValues[]> {
-    const env = DomUtils.encodeToBase64Url({ list_all_destination_networks: true })
+  async loadOptions(config: {
+    optionsType: 'source' | 'destination',
+    endpoint: Endpoint,
+    envData: { [prop: string]: any } | null | undefined,
+    useCache?: boolean | null,
+  }): Promise<OptionValues[]> {
+    const {
+      optionsType, endpoint, envData, useCache,
+    } = config
+    const envString = SchemaParser.getMinionPoolToOptionsQuery(envData, endpoint.type)
+    const callName = optionsType === 'source' ? 'source-minion-pool-options' : 'destination-minion-pool-options'
+    const fieldName = optionsType === 'source' ? 'source_minion_pool_options' : 'destination_minion_pool_options'
+
     const response = await Api.send({
-      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/${platform}-minion-pool-options?env=${env}`,
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpoint.id}/${callName}${envString}`,
       cache: useCache,
+      cancelId: endpoint.id,
     })
-    return response.data[`${platform}_minion_pool_options`]
+    return response.data[fieldName]
   }
 
   async loadMinionPoolSchema(providerName: ProviderTypes, platform: 'source' | 'destination'): Promise<Field[]> {
@@ -160,10 +158,8 @@ class MinionPoolSource {
       const schema = response.data?.schemas?.[`${platform}_minion_pool_environment_schema`]
       let fields = []
       if (schema) {
-        fields = SchemaParser.optionsSchemaToFields(providerName, schema, `${providerName}-minion-pool`)
+        fields = SchemaParser.minionPoolOptionsSchemaToFields(providerName, schema, `${providerName}-minion-pool`)
       }
-      // Remove this field, as all networks are always listed
-      fields = fields.filter(f => f.name !== 'list_all_destination_networks')
       return fields
     } catch (err) {
       console.error(err)
@@ -171,15 +167,21 @@ class MinionPoolSource {
     }
   }
 
-  async add(endpointId: string, data: any, defaultSchema: Field[], envSchema: Field[]) {
+  async add(config: {
+    endpointId: string,
+    data: any,
+    defaultSchema: Field[],
+    envSchema: Field[],
+    provider: ProviderTypes
+  }) {
+    const {
+      endpointId, data, defaultSchema, envSchema, provider,
+    } = config
     const payload = {
       minion_pool: {
-        ...transformFieldsToPayload(defaultSchema, data),
+        ...DefaultMinionPoolSchemaPlugin.getMinionPoolEnv(defaultSchema, data),
         endpoint_id: endpointId,
-        environment_options: {
-          ...transformFieldsToPayload(envSchema, data),
-          list_all_destination_networks: true,
-        },
+        environment_options: SchemaParser.getMinionPoolEnv(provider, envSchema, data),
       },
     }
     const response = await Api.send({
@@ -190,14 +192,19 @@ class MinionPoolSource {
     return response.data.minion_pool
   }
 
-  async update(data: any, defaultSchema: Field[], envSchema: Field[]) {
+  async update(config: {
+    data: any,
+    defaultSchema: Field[],
+    envSchema: Field[],
+    provider: ProviderTypes
+  }) {
+    const {
+      data, defaultSchema, envSchema, provider,
+    } = config
     const payload = {
       minion_pool: {
-        ...transformFieldsToPayload(defaultSchema, data),
-        environment_options: {
-          ...transformFieldsToPayload(envSchema, data),
-          list_all_destination_networks: true,
-        },
+        ...DefaultMinionPoolSchemaPlugin.getMinionPoolEnv(defaultSchema, data),
+        environment_options: SchemaParser.getMinionPoolEnv(provider, envSchema, data),
       },
     }
     const response = await Api.send({

+ 19 - 1
src/sources/Schemas.ts

@@ -12,10 +12,11 @@ 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 { ConnectionSchemaPlugin, OptionsSchemaPlugin } from '../plugins/endpoint'
+import { ConnectionSchemaPlugin, MinionPoolSchemaPlugin, OptionsSchemaPlugin } from '../plugins/endpoint'
 import type { Schema } from '../@types/Schema'
 import type { Endpoint } from '../@types/Endpoint'
 import { ProviderTypes } from '../@types/Providers'
+import { Field } from '../@types/Field'
 
 class SchemaParser {
   static storedConnectionsSchemas: any = {}
@@ -51,6 +52,23 @@ class SchemaParser {
   static parseConnectionResponse(endpoint: Endpoint) {
     return ConnectionSchemaPlugin.for(endpoint.type).parseConnectionResponse(endpoint)
   }
+
+  static getMinionPoolToOptionsQuery(env: any, provider: ProviderTypes) {
+    const parsers = MinionPoolSchemaPlugin.for(provider)
+    return parsers.getMinionPoolToOptionsQuery(env)
+  }
+
+  static minionPoolOptionsSchemaToFields(provider: ProviderTypes, schema: any, dictionaryKey: string) {
+    let fields = this.optionsSchemaToFields(provider, schema, dictionaryKey)
+    const parsers = MinionPoolSchemaPlugin.for(provider)
+    fields = parsers.minionPoolTransformOptionsFields(fields)
+    return fields
+  }
+
+  static getMinionPoolEnv(provider: ProviderTypes, schema: Field[], data: any) {
+    const parsers = MinionPoolSchemaPlugin.for(provider)
+    return parsers.getMinionPoolEnv(schema, data)
+  }
 }
 
 export { SchemaParser }

+ 78 - 32
src/stores/MinionPoolStore.ts

@@ -18,8 +18,11 @@ import {
 import { MinionPool, MinionPoolDetails } from '../@types/MinionPool'
 import MinionPoolSource from '../sources/MinionPoolSource'
 import { Field } from '../@types/Field'
-import { ProviderTypes } from '../@types/Providers'
+import { Providers, ProviderTypes } from '../@types/Providers'
 import { OptionsSchemaPlugin } from '../plugins/endpoint'
+import { providerTypes } from '../constants'
+import apiCaller from '../utils/ApiCaller'
+import { Endpoint, OptionValues } from '../@types/Endpoint'
 
 export type MinionPoolAction = 'allocate' | 'deallocate' | 'refresh'
 
@@ -50,7 +53,10 @@ class MinionPoolStore {
   minionPoolEnvSchema: Field[] = []
 
   @observable
-  loadingEnvOptions: boolean = false
+  optionsPrimaryLoading: boolean = false
+
+  @observable
+  optionsSecondaryLoading: boolean = false
 
   @computed
   get minionPoolCombinedSchema() {
@@ -121,48 +127,88 @@ class MinionPoolStore {
     }
   }
 
-  @action
-  async loadEnvOptions(
-    endpointId: string,
-    providerName: ProviderTypes,
-    platform: 'source' | 'destination',
-    opts?: { useCache?: boolean },
-  ) {
-    this.loadingEnvOptions = true
+  private getOptionsValuesLastReqId: string = ''
+
+  async loadOptions(config: {
+    endpoint: Endpoint,
+    providers: Providers,
+    optionsType: 'source' | 'destination',
+    envData?: { [prop: string]: any } | null,
+    useCache?: boolean,
+  }) {
+    const {
+      optionsType, endpoint, envData, useCache, providers,
+    } = config
+    const providerType = optionsType === 'source' ? providerTypes.SOURCE_OPTIONS : providerTypes.DESTINATION_OPTIONS
+
+    const providerWithExtraOptions = providers[endpoint.type].types.find(t => t === providerType)
+    if (!providerWithExtraOptions) {
+      return
+    }
 
-    try {
-      const options = await MinionPoolSource.loadEnvOptions(endpointId, platform, opts?.useCache)
+    let canceled = false
+    apiCaller.cancelRequests(endpoint.id)
 
-      runInAction(() => {
-        this.minionPoolEnvSchema.forEach(field => {
-          const parser = OptionsSchemaPlugin.for(providerName)
-          parser.fillFieldValues(field, options)
-        })
+    this.optionsPrimaryLoading = !envData
+    this.optionsSecondaryLoading = !!envData
+
+    const reqId = `${(endpoint.id)}-${providerType}`
+    this.getOptionsValuesLastReqId = reqId
+
+    try {
+      const options = await MinionPoolSource.loadOptions({
+        optionsType, endpoint, envData, useCache,
       })
+      this.getOptionsValuesSuccess(
+        endpoint.type,
+        options,
+        this.getOptionsValuesLastReqId === reqId,
+      )
+    } catch (err) {
+      canceled = err ? err.canceled : false
+      throw err
     } finally {
-      runInAction(() => {
-        this.loadingEnvOptions = false
-      })
+      if (!canceled && this.getOptionsValuesLastReqId === reqId) {
+        this.optionsPrimaryLoading = false
+        this.optionsSecondaryLoading = false
+      }
+    }
+  }
+
+  @action getOptionsValuesSuccess(
+    provider: ProviderTypes,
+    options: OptionValues[],
+    isValid: boolean,
+  ) {
+    if (!isValid) {
+      return
     }
+    this.minionPoolEnvSchema.forEach(field => {
+      const parser = OptionsSchemaPlugin.for(provider)
+      parser.fillFieldValues(field, options)
+    })
+    this.minionPoolEnvSchema = [...this.minionPoolEnvSchema]
   }
 
   @action
-  async update(minionPoolData: any) {
-    return MinionPoolSource.update(
-      minionPoolData,
-      this.minionPoolDefaultSchema,
-      this.minionPoolEnvSchema,
-    )
+  async update(provider: ProviderTypes, minionPoolData: any) {
+    return MinionPoolSource.update({
+      data: minionPoolData,
+      defaultSchema: this.minionPoolDefaultSchema,
+      envSchema: this.minionPoolEnvSchema,
+      provider,
+    })
   }
 
   @action
-  async add(endpointId: string, minionPoolData: any) {
-    return MinionPoolSource.add(
+  async add(provider: ProviderTypes, endpointId: string, minionPoolData: any) {
+    return MinionPoolSource.add({
       endpointId,
-      minionPoolData,
-      this.minionPoolDefaultSchema,
-      this.minionPoolEnvSchema,
-    )
+      data: minionPoolData,
+      defaultSchema: this.minionPoolDefaultSchema,
+      envSchema: this.minionPoolEnvSchema,
+      provider,
+    })
   }
 
   @action