Bladeren bron

Allow replica update even if source is down

If source endpoint is down, source options editing is disabled, network
and storage mapping can still be updated, but the source info is
retrieved from replica mapping.
Sergiu Miclea 6 jaren geleden
bovenliggende
commit
ce1e52a604

+ 1 - 2
src/components/molecules/MainDetailsTable/MainDetailsTable.jsx

@@ -33,8 +33,7 @@ import storageIcon from './images/storage.svg'
 import arrowIcon from './images/arrow.svg'
 import arrowIcon from './images/arrow.svg'
 
 
 const Wrapper = styled.div`
 const Wrapper = styled.div`
-  margin-top: 24px;
-  margin-bottom: 48px;
+  margin: 24px 0;
 `
 `
 const ArrowStyled = styled(Arrow)`
 const ArrowStyled = styled(Arrow)`
   position: absolute;
   position: absolute;

+ 9 - 2
src/components/molecules/Panel/Panel.jsx

@@ -32,12 +32,12 @@ const Navigation = styled.div`
 const NavigationItemDiv = styled.div`
 const NavigationItemDiv = styled.div`
   height: 47px;
   height: 47px;
   border-bottom: 1px solid ${Palette.grayscale[2]};
   border-bottom: 1px solid ${Palette.grayscale[2]};
-  color: black;
+  color: ${props => props.disabled ? Palette.grayscale[3] : 'black'};
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   padding: 0 24px;
   padding: 0 24px;
   font-size: 18px;
   font-size: 18px;
-  cursor: pointer;
+  cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
   ${props => props.selected ? css`
   ${props => props.selected ? css`
     color: ${Palette.primary};
     color: ${Palette.primary};
     background: ${Palette.grayscale[2]};
     background: ${Palette.grayscale[2]};
@@ -65,6 +65,8 @@ const ReloadButton = styled.div`
 export type NavigationItem = {
 export type NavigationItem = {
   label: string,
   label: string,
   value: string,
   value: string,
+  disabled?: boolean,
+  title?: string,
 }
 }
 
 
 export type Props = {
 export type Props = {
@@ -81,6 +83,9 @@ export const TEST_ID = 'panel'
 @observer
 @observer
 class Panel extends React.Component<Props> {
 class Panel extends React.Component<Props> {
   handleItemClick(item: NavigationItem) {
   handleItemClick(item: NavigationItem) {
+    if (item.disabled) {
+      return
+    }
     if (item.value !== this.props.selectedValue) {
     if (item.value !== this.props.selectedValue) {
       this.props.onChange(item)
       this.props.onChange(item)
     }
     }
@@ -96,6 +101,8 @@ class Panel extends React.Component<Props> {
               selected={this.props.selectedValue ? this.props.selectedValue === item.value : i === 0}
               selected={this.props.selectedValue ? this.props.selectedValue === item.value : i === 0}
               onClick={() => { this.handleItemClick(item) }}
               onClick={() => { this.handleItemClick(item) }}
               data-test-id={`${TEST_ID}-navItem-${item.value}`}
               data-test-id={`${TEST_ID}-navItem-${item.value}`}
+              disabled={item.disabled}
+              title={item.title}
             >{item.label}</NavigationItemDiv>
             >{item.label}</NavigationItemDiv>
           ))}
           ))}
         </Navigation>
         </Navigation>

+ 24 - 6
src/components/organisms/EditReplica/EditReplica.jsx

@@ -88,6 +88,7 @@ type State = {
   updateDisabled: boolean,
   updateDisabled: boolean,
   selectedNetworks: NetworkMap[],
   selectedNetworks: NetworkMap[],
   storageMap: StorageMap[],
   storageMap: StorageMap[],
+  sourceFailed: boolean,
 }
 }
 
 
 @observer
 @observer
@@ -99,6 +100,7 @@ class EditReplica extends React.Component<Props, State> {
     updateDisabled: false,
     updateDisabled: false,
     selectedNetworks: [],
     selectedNetworks: [],
     storageMap: [],
     storageMap: [],
+    sourceFailed: false,
   }
   }
 
 
   scrollableRef: HTMLElement
   scrollableRef: HTMLElement
@@ -118,8 +120,18 @@ class EditReplica extends React.Component<Props, State> {
 
 
     let loadAllOptions = async (type: 'source' | 'destination') => {
     let loadAllOptions = async (type: 'source' | 'destination') => {
       let endpoint = type === 'source' ? this.props.sourceEndpoint : this.props.destinationEndpoint
       let endpoint = type === 'source' ? this.props.sourceEndpoint : this.props.destinationEndpoint
-      await this.loadOptions(endpoint, type, useCache)
-      this.loadExtraOptions(null, type, useCache)
+      try {
+        await this.loadOptions(endpoint, type, useCache)
+        this.loadExtraOptions(null, type, useCache)
+      } catch (err) {
+        if (type === 'source') {
+          let selectedPanel = this.state.selectedPanel
+          if (selectedPanel === 'source_options') {
+            selectedPanel = 'dest_options'
+          }
+          this.setState({ sourceFailed: true, selectedPanel })
+        }
+      }
     }
     }
     if (this.hasSourceOptions()) {
     if (this.hasSourceOptions()) {
       loadAllOptions('source')
       loadAllOptions('source')
@@ -359,14 +371,15 @@ class EditReplica extends React.Component<Props, State> {
     return selectedNetworks
     return selectedNetworks
   }
   }
 
 
-  getStorageMap(): StorageMap[] {
+  getStorageMap(storageBackends: StorageBackend[]): StorageMap[] {
     let storageMap: StorageMap[] = []
     let storageMap: StorageMap[] = []
     let currentStorage = this.props.replica.storage_mappings || {}
     let currentStorage = this.props.replica.storage_mappings || {}
     let buildStorageMap = (type: 'backend' | 'disk', mapping: any) => {
     let buildStorageMap = (type: 'backend' | 'disk', mapping: any) => {
+      let backend = storageBackends.find(b => b.name === mapping.destination)
       return {
       return {
         type,
         type,
         source: { storage_backend_identifier: mapping.source, id: mapping.disk_id },
         source: { storage_backend_identifier: mapping.source, id: mapping.disk_id },
-        target: { name: mapping.destination, id: mapping.destination },
+        target: { name: mapping.destination, id: backend ? backend.id : mapping.destination },
       }
       }
     }
     }
     let backendMappings = currentStorage.backend_mappings || []
     let backendMappings = currentStorage.backend_mappings || []
@@ -445,7 +458,7 @@ class EditReplica extends React.Component<Props, State> {
       <WizardStorage
       <WizardStorage
         storageBackends={endpointStore.storageBackends}
         storageBackends={endpointStore.storageBackends}
         instancesDetails={this.props.instancesDetails}
         instancesDetails={this.props.instancesDetails}
-        storageMap={this.getStorageMap()}
+        storageMap={this.getStorageMap(endpointStore.storageBackends)}
         onChange={(s, t, type) => { this.handleStorageChange(s, t, type) }}
         onChange={(s, t, type) => { this.handleStorageChange(s, t, type) }}
         style={{ padding: '32px 32px 0 32px', width: 'calc(100% - 64px)' }}
         style={{ padding: '32px 32px 0 32px', width: 'calc(100% - 64px)' }}
       />
       />
@@ -527,7 +540,12 @@ class EditReplica extends React.Component<Props, State> {
     }
     }
 
 
     if (this.hasSourceOptions()) {
     if (this.hasSourceOptions()) {
-      navigationItems.splice(0, 0, { value: 'source_options', label: 'Source Options' })
+      navigationItems.splice(0, 0, {
+        value: 'source_options',
+        label: 'Source Options',
+        disabled: this.state.sourceFailed,
+        title: this.state.sourceFailed ? 'There are source platform errors, source options can\'t be updated' : '',
+      })
     }
     }
 
 
     return (
     return (

+ 1 - 0
src/components/organisms/MigrationDetailsContent/MigrationDetailsContent.jsx

@@ -35,6 +35,7 @@ const Wrapper = styled.div`
 `
 `
 
 
 const Buttons = styled.div`
 const Buttons = styled.div`
+  margin-top: 24px;
   & > button:last-child {
   & > button:last-child {
     float: right;
     float: right;
   }
   }

+ 1 - 0
src/components/organisms/ReplicaDetailsContent/ReplicaDetailsContent.jsx

@@ -41,6 +41,7 @@ const Wrapper = styled.div`
 const Buttons = styled.div`
 const Buttons = styled.div`
   display: flex;
   display: flex;
   justify-content: space-between;
   justify-content: space-between;
+  margin-top: 24px;
 `
 `
 const ButtonColumn = styled.div`
 const ButtonColumn = styled.div`
   display: flex;
   display: flex;

+ 9 - 1
src/components/organisms/WizardNetworks/WizardNetworks.jsx

@@ -230,6 +230,10 @@ class WizardNetworks extends React.Component<Props> {
       })
       })
     })
     })
 
 
+    if (nics.length === 0 && this.props.selectedNetworks && this.props.selectedNetworks.length) {
+      nics = this.props.selectedNetworks.map(n => n.sourceNic)
+    }
+
     if (nics.length === 0) {
     if (nics.length === 0) {
       return this.renderNoNics()
       return this.renderNoNics()
     }
     }
@@ -252,7 +256,11 @@ class WizardNetworks extends React.Component<Props> {
               <NetworkImage />
               <NetworkImage />
               <NetworkTitle>
               <NetworkTitle>
                 <NetworkName data-test-id={`wNetworks-networkName-${nic.id}`}>{nic.network_name}</NetworkName>
                 <NetworkName data-test-id={`wNetworks-networkName-${nic.id}`}>{nic.network_name}</NetworkName>
-                <NetworkSubtitle data-test-id={`wNetworks-connectedTo-${nic.id}`}>{`Connected to ${connectedTo.join(', ')}`}</NetworkSubtitle>
+                {connectedTo.length ? (
+                  <NetworkSubtitle data-test-id={`wNetworks-connectedTo-${nic.id}`}>
+                    {`Connected to ${connectedTo.join(', ')}`}
+                  </NetworkSubtitle>
+                ) : null}
               </NetworkTitle>
               </NetworkTitle>
               <ArrowImage />
               <ArrowImage />
               <Dropdowns>
               <Dropdowns>

+ 13 - 6
src/components/organisms/WizardStorage/WizardStorage.jsx

@@ -118,7 +118,7 @@ const NoStorageSubtitle = styled.div`
   text-align: center;
   text-align: center;
 `
 `
 
 
-export const getDisks = (instancesDetails: Instance[], type: 'backend' | 'disk'): Disk[] => {
+export const getDisks = (instancesDetails: Instance[], type: 'backend' | 'disk', storageMap?: ?StorageMap[]): Disk[] => {
   let fieldName = type === 'backend' ? 'storage_backend_identifier' : 'id'
   let fieldName = type === 'backend' ? 'storage_backend_identifier' : 'id'
 
 
   let disks = []
   let disks = []
@@ -133,6 +133,9 @@ export const getDisks = (instancesDetails: Instance[], type: 'backend' | 'disk')
       disks.push(disk)
       disks.push(disk)
     })
     })
   })
   })
+  if (disks.length === 0 && storageMap && storageMap.length) {
+    disks = storageMap.map(sm => sm.source)
+  }
   return disks
   return disks
 }
 }
 
 
@@ -207,9 +210,13 @@ class WizardStorage extends React.Component<Props> {
                   >
                   >
                     {diskNameParsed[0]}
                     {diskNameParsed[0]}
                   </StorageName>
                   </StorageName>
-                  <StorageSubtitle
-                    data-test-id={`${TEST_ID}-${type}-connectedTo`}
-                  >{`Connected to ${connectedTo.join(', ')}`}</StorageSubtitle>
+                  {connectedTo.length ? (
+                    <StorageSubtitle
+                      data-test-id={`${TEST_ID}-${type}-connectedTo`}
+                    >
+                      {`Connected to ${connectedTo.join(', ')}`}
+                    </StorageSubtitle>
+                  ) : null}
                 </StorageTitle>
                 </StorageTitle>
                 <ArrowImage />
                 <ArrowImage />
                 {storageItems.length > 10 ? (
                 {storageItems.length > 10 ? (
@@ -245,7 +252,7 @@ class WizardStorage extends React.Component<Props> {
   }
   }
 
 
   renderBackendMapping() {
   renderBackendMapping() {
-    let disks = getDisks(this.props.instancesDetails, 'backend')
+    let disks = getDisks(this.props.instancesDetails, 'backend', this.props.storageMap)
 
 
     if (disks.length === 0 || this.props.storageBackends.length === 0) {
     if (disks.length === 0 || this.props.storageBackends.length === 0) {
       return null
       return null
@@ -255,7 +262,7 @@ class WizardStorage extends React.Component<Props> {
   }
   }
 
 
   renderDiskMapping() {
   renderDiskMapping() {
-    let disks = getDisks(this.props.instancesDetails, 'disk')
+    let disks = getDisks(this.props.instancesDetails, 'disk', this.props.storageMap)
 
 
     if (disks.length === 0 || this.props.storageBackends.length === 0) {
     if (disks.length === 0 || this.props.storageBackends.length === 0) {
       return this.renderNoStorage()
       return this.renderNoStorage()

+ 3 - 13
src/stores/ProviderStore.js

@@ -98,7 +98,6 @@ class ProviderStore {
   @observable sourceSchemaLoading: boolean = false
   @observable sourceSchemaLoading: boolean = false
 
 
   lastDestinationSchemaType: 'replica' | 'migration' = 'replica'
   lastDestinationSchemaType: 'replica' | 'migration' = 'replica'
-  lastSourceSchemaType: 'replica' | 'migration' = 'replica'
 
 
   @computed
   @computed
   get providerNames(): string[] {
   get providerNames(): string[] {
@@ -155,13 +154,8 @@ class ProviderStore {
     optionsType: 'source' | 'destination',
     optionsType: 'source' | 'destination',
     useCache?: boolean,
     useCache?: boolean,
     quietError?: boolean,
     quietError?: boolean,
-  }): Promise<void> {
+  }): Promise<Field[]> {
     let { schemaType, providerName, optionsType, useCache, quietError } = options
     let { schemaType, providerName, optionsType, useCache, quietError } = options
-    if (optionsType === 'source') {
-      this.lastSourceSchemaType = schemaType
-    } else {
-      this.lastDestinationSchemaType = schemaType
-    }
 
 
     if (optionsType === 'source') {
     if (optionsType === 'source') {
       this.sourceSchemaLoading = true
       this.sourceSchemaLoading = true
@@ -172,6 +166,7 @@ class ProviderStore {
     try {
     try {
       let fields: Field[] = await ProviderSource.loadOptionsSchema(providerName, schemaType, optionsType, useCache, quietError)
       let fields: Field[] = await ProviderSource.loadOptionsSchema(providerName, schemaType, optionsType, useCache, quietError)
       this.loadOptionsSchemaSuccess(fields, optionsType)
       this.loadOptionsSchemaSuccess(fields, optionsType)
+      return fields
     } catch (err) {
     } catch (err) {
       throw err
       throw err
     } finally {
     } finally {
@@ -229,12 +224,7 @@ class ProviderStore {
       if (canceled) {
       if (canceled) {
         return optionsType === 'source' ? [...this.sourceOptions] : [...this.destinationOptions]
         return optionsType === 'source' ? [...this.sourceOptions] : [...this.destinationOptions]
       }
       }
-      let schemaType = optionsType === 'source' ? this.lastSourceSchemaType : this.lastDestinationSchemaType
-      if (!envData) {
-        return []
-      }
-      let newOptions = await this.loadOptionsSchema({ providerName, schemaType, optionsType })
-      return newOptions
+      throw err
     } finally {
     } finally {
       if (!canceled) {
       if (!canceled) {
         this.getOptionsValuesDone(optionsType, !envData)
         this.getOptionsValuesDone(optionsType, !envData)