2
0
Эх сурвалжийг харах

Merge pull request #529 from smiclea/azure-migrate

Re-add Azure Migrate support
Nashwan Azhari 6 жил өмнө
parent
commit
cf2d3c4411

+ 18 - 5
server/proxy.js

@@ -18,7 +18,7 @@ import axios from 'axios'
 
 const forwardHeaders = ['authorization']
 
-let buildError = message => {
+let buildError = (message) => {
   return {
     error: { message: `Proxy - ${message}` },
   }
@@ -28,17 +28,29 @@ module.exports = app => {
   const jsonParser = bodyParser.json()
 
   app.post('/azure-login', jsonParser, (req, res) => {
-    MsRest.loginWithUsernamePassword(req.body.username, req.body.password, (err, credentials) => {
+    let handleResponse = (err, credentials) => {
       if (err) {
-        res.status(401).send(buildError(`Couldn't authenticate user: ${req.body.username}`))
+        console.log(err)
+        res.status(401).send(buildError('Azure API authentication error'))
       } else {
         res.send(credentials)
       }
-    })
+    }
+    let connInfo = req.body
+    let userCred = connInfo.user_credentials
+    let servicePrin = connInfo.service_principal_credentials
+    if (userCred && userCred.username && userCred.password) {
+      MsRest.loginWithUsernamePassword(userCred.username, userCred.password, handleResponse)
+    } else if (servicePrin && servicePrin.client_id && servicePrin.client_secret) {
+      MsRest.loginWithServicePrincipalSecret(servicePrin.client_id, servicePrin.client_secret, connInfo.tenant, handleResponse)
+    } else {
+      res.status(401).send(buildError('Azure API authentication error'))
+    }
   })
 
   app.get('/proxy/*', (req, res) => {
-    let url = req.url.substr('/proxy/'.length)
+    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+    let url = Buffer.from(req.url.substr('/proxy/'.length), 'base64').toString()
     let headers = {}
     forwardHeaders.forEach(headerName => {
       if (req.headers[headerName] != null) {
@@ -52,6 +64,7 @@ module.exports = app => {
       if (error.response) {
         res.status(error.response.status).send(buildError(error.response.data.error.message))
       } else if (error.request) {
+        console.log(error)
         res.status(500).send(buildError('No Response!'))
       } else {
         res.status(500).send(buildError('Error creating request!'))

+ 1 - 3
src/components/atoms/SmallLoading/SmallLoading.jsx

@@ -45,9 +45,7 @@ const ProgressText = styled.div`
   width: 100%;
   text-align: center;
 `
-const CircleProgressBar = styled.circle`
-  transition: stroke-dashoffset ${StyleProps.animations.swift};
-`
+const CircleProgressBar = styled.circle``
 
 export const TEST_ID = 'smallLoading'
 

+ 1 - 1
src/components/atoms/SmallLoading/story.jsx

@@ -20,7 +20,7 @@ import SmallLoading from '.'
 
 storiesOf('SmallLoading', module)
   .add('default', () => (
-    <SmallLoading loadingProgress={75} />
+    <SmallLoading loadingProgress={8} />
   ))
   .add('spinning', () => (
     <SmallLoading loadingProgress={-1} />

+ 2 - 2
src/components/molecules/AssessedVmListItem/AssessedVmListItem.jsx

@@ -106,10 +106,10 @@ class AssessedVmListItem extends React.Component<Props> {
               onChange={checked => { this.props.onSelectedChange(this.props.item, checked) }}
               disabled={this.props.disabled}
             />
-            <DisplayNameLabel>{`${this.props.item.properties.datacenterContainer}/${this.props.item.properties.displayName}`}</DisplayNameLabel>
+            <DisplayNameLabel>{`${this.props.item.properties.displayName}`}</DisplayNameLabel>
           </DisplayName>
           <Value width="25%">
-            {this.props.item.properties.operatingSystem}
+            {this.props.item.properties.operatingSystemName}
           </Value>
           <Value width="25%">
             {standardCount} Standard, {premiumCount} Premium

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

@@ -72,6 +72,7 @@ const AssessmentType = styled.div`
   justify-content: center;
   align-items: center;
   margin-right: 46px;
+  ${StyleProps.exactWidth('180px')}
 `
 const AssessmentImage = styled.div`
   width: 48px;
@@ -89,7 +90,7 @@ const TotalVms = styled.div`
   margin-right: 48px;
 `
 const Project = styled.div`
-  ${StyleProps.exactWidth('132px')}
+  ${StyleProps.exactWidth('175px')}
   margin-right: 48px;
 `
 const ItemLabel = styled.div`

+ 1 - 1
src/components/molecules/DropdownFilter/DropdownFilter.jsx

@@ -43,7 +43,7 @@ const List = styled.div`
   position: absolute;
   top: 24px;
   right: -7px;
-  z-index: 20;
+  z-index: 9999;
   padding: 8px;
   background: ${Palette.grayscale[1]};
   border-radius: 4px;

+ 5 - 5
src/components/organisms/AssessmentDetailsContent/AssessmentDetailsContent.jsx

@@ -219,11 +219,11 @@ class AssessmentDetailsContent extends React.Component<Props> {
     }
 
     if (this.props.instances.length > 0 &&
-      !this.props.instances.find(i => i.id === `${vm.properties.datacenterMachineId}`)) {
+      !this.props.instances.find(i => i.name === `${vm.properties.displayName}`)) {
       return false
     }
 
-    return this.props.sourceEndpoint.connection_info.host === vm.properties.datacenterManagementServer
+    return this.props.sourceEndpoint.connection_info.host === vm.properties.datacenterManagementServerName
   }
 
   renderBottomControls() {
@@ -246,7 +246,7 @@ class AssessmentDetailsContent extends React.Component<Props> {
     let status = this.props.item ?
       this.props.item.properties.status === 'Completed' ? 'Ready for Migration' : this.props.item.properties.status : ''
 
-    let locationItem: ?Location = this.props.locations.find(l => l.id.toLowerCase() === (this.props.selectedLocation ? this.props.selectedLocation.toLowerCase() : null))
+    let locationItem: ?Location = this.props.locations.find(l => l.id === this.props.selectedLocation)
 
     return (
       <Columns>
@@ -348,14 +348,14 @@ class AssessmentDetailsContent extends React.Component<Props> {
       return (
         <AssessedVmListItem
           item={vm}
-          selected={this.props.selectedVms.filter(m => m === vm.properties.datacenterMachineId).length > 0}
+          selected={this.props.selectedVms.filter(m => m === vm.properties.displayName).length > 0}
           onSelectedChange={(vm, selected) => { this.props.onVmSelectedChange(vm, selected) }}
           disabled={!this.doesVmMatchSource(vm)}
           loadingVmSizes={this.props.loadingVmSizes}
           recommendedVmSize={vm.properties.recommendedSize}
           vmSizes={this.props.vmSizes}
           selectedVmSize={this.props.onGetSelectedVmSize(vm)}
-          onVmSizeChange={size => { this.props.onVmSizeChange(vm.properties.datacenterMachineId, size) }}
+          onVmSizeChange={size => { this.props.onVmSizeChange(vm.properties.displayName, size) }}
         />
       )
     })

+ 2 - 0
src/components/organisms/AssessmentMigrationOptions/AssessmentMigrationOptions.jsx

@@ -25,6 +25,7 @@ import ToggleButtonBar from '../../../components/atoms/ToggleButtonBar'
 import type { Field } from '../../../types/Field'
 
 import StyleProps from '../../styleUtils/StyleProps'
+import LabelDictionary from '../../../utils/LabelDictionary'
 
 import assessmentImage from './images/assessment.svg'
 
@@ -227,6 +228,7 @@ class AssessmentMigrationOptions extends React.Component<Props, State> {
           width={StyleProps.inputSizes.large.width}
           {...field}
           {...additionalProps}
+          label={field.label || LabelDictionary.get(field.name)}
         />
       )
       const pushRow = (field1: React.Node, field2?: React.Node) => {

+ 59 - 24
src/components/pages/AssessmentDetailsPage/AssessmentDetailsPage.jsx

@@ -104,8 +104,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
       return []
     }
     let endpoints = connectionsInfo.filter(
-      // $FlowIgnore
-      endpoint => vms.find(vm => vm.properties.datacenterManagementServer.toLowerCase() === endpoint.connection_info.host.toLowerCase())
+      endpoint => vms.find(vm => endpoint.connection_info.host && endpoint.connection_info.host.toLowerCase() === vm.properties.datacenterManagementServerName.toLowerCase())
     )
     return endpoints
   }
@@ -129,13 +128,13 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
       vms = azureStore.assessedVms
     }
     return vms.filter(vm =>
-      `${vm.properties.datacenterContainer}/${vm.properties.displayName}`.toLowerCase().indexOf(this.state.vmSearchValue.toLowerCase()) > -1
+      `${vm.properties.displayName}`.toLowerCase().indexOf(this.state.vmSearchValue.toLowerCase()) > -1
     )
   }
 
   getSourceEndpointId() {
     let localData = this.getLocalData()
-    return localData.sourceEndpoint ? localData.sourceEndpoint.id : ''
+    return localData.sourceEndpoint ? localData.sourceEndpoint.id : null
   }
 
   getEnabledVms() {
@@ -149,8 +148,8 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
       return []
     }
     return azureStore.assessedVms.filter(vm => {
-      if (vm.properties.datacenterManagementServer.toLowerCase() === sourceHost.toLowerCase() &&
-        instanceStore.instances.find(i => i.id === `${vm.properties.datacenterMachineId}`)) {
+      if (vm.properties.datacenterManagementServerName.toLowerCase() === sourceHost.toLowerCase() &&
+        instanceStore.instances.find(i => i.name === `${vm.properties.displayName}`)) {
         return true
       }
       return false
@@ -167,30 +166,59 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
 
   handleVmSelectedChange(vm: VmItem, selected: boolean) {
     let selectedVms = this.getLocalData().selectedVms
+    let instanceInfo = instanceStore.instances.find(i => i.name === vm.properties.displayName)
     if (selected) {
       selectedVms = [
         ...selectedVms,
-        vm.properties.datacenterMachineId,
+        vm.properties.displayName,
       ]
       azureStore.updateSelectedVms(selectedVms)
-      this.loadInstancesDetails()
+      if (instanceStore.loadingInstancesDetails) {
+        this.loadInstancesDetails()
+        return
+      }
+      let sourceEndpointId = this.getSourceEndpointId()
+      if (!sourceEndpointId || !instanceInfo) {
+        return
+      }
+      let localData = this.getLocalData()
+      instanceStore.addInstanceDetails({
+        endpointId: sourceEndpointId,
+        instanceInfo,
+        cache: true,
+        env: {
+          location: localData.locationName,
+          resource_group: localData.resourceGroupName,
+        },
+        targetProvider: 'azure',
+      })
     } else {
-      selectedVms = this.getLocalData().selectedVms.filter(m => m !== vm.properties.datacenterMachineId)
+      selectedVms = selectedVms.filter(m => m !== vm.properties.displayName)
+      azureStore.updateSelectedVms(selectedVms)
+      if (instanceStore.loadingInstancesDetails) {
+        this.loadInstancesDetails()
+        return
+      }
+      if (instanceInfo) {
+        instanceStore.removeInstanceDetails(instanceInfo)
+      }
     }
-    azureStore.updateSelectedVms(selectedVms)
-    this.loadInstancesDetails()
   }
 
   handleSelectAllVmsChange(selected: boolean) {
     let selectedVms = selected ? [...this.getFilteredAssessedVms(this.getEnabledVms())] : []
-    azureStore.updateSelectedVms(selectedVms.map(v => v.properties.datacenterMachineId))
+    azureStore.updateSelectedVms(selectedVms.map(v => v.properties.displayName))
     this.loadInstancesDetails()
   }
 
   handleSourceEndpointChange(sourceEndpoint: ?Endpoint) {
     this.setState({ selectedNetworks: [] })
     azureStore.updateSourceEndpoint(sourceEndpoint)
-    instanceStore.loadInstances(this.getSourceEndpointId()).then(() => {
+    let sourceEndpointId = this.getSourceEndpointId()
+    if (!sourceEndpointId) {
+      return
+    }
+    instanceStore.loadInstances(sourceEndpointId).then(() => {
       this.initSelectedVms()
       this.loadInstancesDetails()
     })
@@ -267,7 +295,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
   }
 
   handleGetVmSize(vm: VmItem): string {
-    return this.getLocalData().selectedVmSizes[vm.properties.datacenterMachineId]
+    return this.getLocalData().selectedVmSizes[vm.properties.displayName]
   }
 
   handleVmSearchValueChange(vmSearchValue: string) {
@@ -276,7 +304,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
 
   azureAuthenticate() {
     let connectionInfo = this.getUrlInfo().connectionInfo
-    azureStore.authenticate(connectionInfo.user_credentials.username, connectionInfo.user_credentials.password).then(() => {
+    azureStore.authenticate(connectionInfo).then(() => {
       this.loadAssessmentDetails()
     })
   }
@@ -324,6 +352,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
       optionsType: 'destination',
       endpointId: localData.endpoint.id,
       providerName: localData.endpoint.type,
+      allowMultiple: true,
     }).then(options => {
       let locations = options.find(o => o.name === 'location')
       if (locations && locations.values) {
@@ -356,6 +385,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
         location: localData.locationName,
         resource_group: localData.resourceGroupName,
       },
+      allowMultiple: true,
     }).then(options => {
       let vmSizes = options.find(o => o.name === 'vm_size')
       if (vmSizes && vmSizes.values) {
@@ -367,7 +397,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
 
   initSelectedVms() {
     let localData = this.getLocalData()
-    let enabledVms = this.getEnabledVms().map(vm => vm.properties.datacenterMachineId)
+    let enabledVms = this.getEnabledVms().map(vm => vm.properties.displayName)
     if (localData.selectedVms.length === 0) {
       azureStore.updateSelectedVms(enabledVms)
     } else {
@@ -381,7 +411,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
     let localData = this.getLocalData()
 
     vms.forEach(vm => {
-      vmSizes[vm.properties.datacenterMachineId] = localData.selectedVmSizes[vm.properties.datacenterMachineId] || vm.properties.recommendedSize || 'auto'
+      vmSizes[vm.properties.displayName] = localData.selectedVmSizes[vm.properties.displayName] || vm.properties.recommendedSize || 'auto'
     })
     azureStore.updateVmSizes(vmSizes)
   }
@@ -398,12 +428,17 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
   loadInstancesDetails() {
     let localData = this.getLocalData()
     let selectedVms = localData.selectedVms
-    let instancesInfo = instanceStore.instances.filter(i => selectedVms.find(m => i.id === m))
+    let instancesInfo = instanceStore.instances.filter(i => selectedVms.find(m => i.name === m))
     instanceStore.clearInstancesDetails()
+    let sourceEndpointId = this.getSourceEndpointId()
+    if (!sourceEndpointId) {
+      return
+    }
     instanceStore.loadInstancesDetails({
-      endpointId: this.getSourceEndpointId(),
+      endpointId: sourceEndpointId,
       instancesInfo,
-      cache: true,
+      // cache: true,
+      skipLog: true,
       env: {
         location: localData.locationName,
         resource_group: localData.resourceGroupName,
@@ -414,12 +449,12 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
 
   handleMigrationExecute(fieldValues: { [string]: any }) {
     let selectedVms = this.getLocalData().selectedVms
-    let selectedInstances = instanceStore.instancesDetails.filter(i => selectedVms.find(m => i.id === m))
+    let selectedInstances = instanceStore.instancesDetails.filter(i => selectedVms.find(m => i.name === m))
     let vmSizes = {}
     let localData = this.getLocalData()
     selectedInstances.forEach(i => {
-      let vm = selectedVms.find(m => i.id === m)
-      let selectedVmSize = localData.selectedVmSizes[i.id]
+      let vm = selectedVms.find(m => i.name === m)
+      let selectedVmSize = localData.selectedVmSizes[i.name]
       if (vm && azureStore.vmSizes.find(s => s === selectedVmSize)) {
         vmSizes[i.instance_name || i.name] = selectedVmSize
       }
@@ -490,7 +525,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
               targetEndpointsLoading={endpointStore.loading}
               loadingVmSizes={this.state.loadingTargetVmSizes}
               sourceEndpointsLoading={endpointsLoading}
-              targetOptionsLoading={providerStore.destinationOptionsPrimaryLoading || providerStore.destinationOptionsSecondaryLoading}
+              targetOptionsLoading={providerStore.destinationOptionsSecondaryLoading}
               targetEndpoints={this.getTargetEndpoints()}
               targetEndpoint={localData.endpoint}
               onTargetEndpointChange={endpoint => { this.handleTargetEndpointChange(endpoint) }}

+ 1 - 2
src/components/pages/AssessmentsPage/AssessmentsPage.jsx

@@ -222,8 +222,7 @@ class AssessmentsPage extends React.Component<Props, State> {
       if (!connectionInfo) {
         return
       }
-      // $FlowIgnore
-      azureStore.authenticate(connectionInfo.user_credentials.username, connectionInfo.user_credentials.password).then(() => {
+      azureStore.authenticate(connectionInfo).then(() => {
         // $FlowIgnore
         azureStore.getResourceGroups(connectionInfo.subscription_id).then(() => {
           let groups = azureStore.assessmentResourceGroups

+ 10 - 6
src/sources/AssessmentSource.js

@@ -21,14 +21,18 @@ import configLoader from '../utils/Config'
 import notificationStore from '../stores/NotificationStore'
 
 class AssessmentSourceUtils {
-  static getDestinationEnv(data: MigrationInfo) {
-    let env = {}
+  static getNetworkMap(data: MigrationInfo) {
+    let networkMap = {}
     if (data.networks && data.networks.length) {
-      env.network_map = {}
       data.networks.forEach(mapping => {
-        env.network_map[mapping.sourceNic.network_name] = mapping.targetNetwork.name
+        networkMap[mapping.sourceNic.network_name] = mapping.targetNetwork.name
       })
     }
+    return networkMap
+  }
+
+  static getDestinationEnv(data: MigrationInfo) {
+    let env = {}
     let vmSize = data.vmSizes[Object.keys(data.vmSizes).filter(k => k === data.selectedInstances[0].instance_name)[0]]
     if (vmSize) {
       env.vm_size = vmSize
@@ -53,14 +57,14 @@ class AssessmentSource {
       destination_endpoint_id: data.target.id,
       destination_environment: AssessmentSourceUtils.getDestinationEnv(data),
       instances: data.selectedInstances.map(i => i.instance_name),
+      network_map: AssessmentSourceUtils.getNetworkMap(data),
       notes: '',
-      security_groups: ['testgroup'],
+      replication_count: 2,
     }
 
     if (type === 'migration') {
       payload[type].skip_os_morphing = data.fieldValues.skip_os_morphing
     }
-
     return Api.send({
       url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/${type}s`,
       method: 'POST',

+ 21 - 12
src/sources/AzureSource.js

@@ -20,10 +20,10 @@ import Api from '../utils/ApiCaller'
 import type { Assessment, VmItem, VmSize } from '../types/Assessment'
 
 const azureUrl = 'https://management.azure.com/'
-const defaultApiVersion = '2017-11-11-preview'
+const defaultApiVersion = '2019-10-01'
 
 const resourceGroupsUrl = (opts: { subscriptionId: string }) => `/subscriptions/${opts.subscriptionId}/resourceGroups`
-const projectsUrl = ({ resourceGroupName, ...other }) => `${resourceGroupsUrl({ ...other })}/${resourceGroupName}/providers/Microsoft.Migrate/projects`
+const projectsUrl = ({ resourceGroupName, ...other }) => `${resourceGroupsUrl({ ...other })}/${resourceGroupName}/providers/Microsoft.Migrate/assessmentprojects`
 const groupsUrl = ({ projectName, ...other }) => `${projectsUrl({ ...other })}/${projectName}/groups`
 const assessmentsUrl = ({ groupName, ...other }) => `${groupsUrl({ ...other })}/${groupName}/assessments`
 const assessmentDetailsUrl = ({ assessmentName, ...other }) => `${assessmentsUrl({ ...other })}/${assessmentName}`
@@ -31,7 +31,7 @@ const assessedVmsUrl = ({ ...other }) => `${assessmentDetailsUrl({ ...other })}/
 
 class Util {
   static buildUrl(baseUrl: string, apiVersion?: string): string {
-    const url = `/proxy/${azureUrl + baseUrl}?api-version=${apiVersion || defaultApiVersion}`
+    const url = `/proxy/${btoa(`${azureUrl + baseUrl}?api-version=${apiVersion || defaultApiVersion}`)}`
     return url
   }
 
@@ -75,11 +75,11 @@ class Util {
 }
 
 class AzureSource {
-  static authenticate(username: string, password: string): Promise<any> {
+  static authenticate(connectionInfo: any): Promise<any> {
     return Api.send({
       url: '/azure-login',
       method: 'POST',
-      data: { username, password },
+      data: connectionInfo,
     }).then(response => {
       let entries = Object.keys(response.data.tokenCache)[0]
       let accessToken = response.data.tokenCache[entries][0].accessToken
@@ -112,7 +112,7 @@ class AzureSource {
       if (!Util.isResponseValid(projectsResponse)) {
         return []
       }
-      let projects = projectsResponse.data.value.filter(p => p.type === 'Microsoft.Migrate/projects')
+      let projects = projectsResponse.data.value.filter(p => p.type === 'Microsoft.Migrate/assessmentprojects')
 
       // Load groups for each project
       return Promise.all(projects.map(project => {
@@ -142,7 +142,15 @@ class AzureSource {
           if (!Util.isResponseValid(assessmentResponse)) {
             return null
           }
-          return assessmentResponse.data.value.map(assessment => { return { ...assessment, group, project: group.project } })
+          return assessmentResponse.data.value.map((assessment: Assessment) => ({
+            ...assessment,
+            group,
+            project: group.project,
+            properies: {
+              ...assessment.properties,
+              azureLocation: assessment.properties.azureLocation.toLowerCase(),
+            },
+          }))
         })
       }))
     }).then(assessementsResponses => {
@@ -154,10 +162,11 @@ class AzureSource {
     })
   }
 
-  static getAssessmentDetails(info: Assessment): Promise<Assessment> {
-    return Api.get(Util.buildUrl(assessmentDetailsUrl({ ...info, subscriptionId: info.connectionInfo.subscription_id }))).then(response => {
-      return Util.validateResponse(response, { ...response.data, ...info })
-    })
+  static async getAssessmentDetails(info: Assessment): Promise<Assessment> {
+    const response = await Api.get(Util.buildUrl(assessmentDetailsUrl({ ...info, subscriptionId: info.connectionInfo.subscription_id })))
+    let assessment: Assessment = await Util.validateResponse(response, { ...response.data, ...info })
+    assessment.properties.azureLocation = assessment.properties.azureLocation.toLowerCase()
+    return assessment
   }
 
   static getAssessedVms(info: Assessment): Promise<VmItem[]> {
@@ -168,7 +177,7 @@ class AzureSource {
 
       let vms = response.data.value
       vms.sort((a, b) => {
-        let getLabel = item => `${item.properties.datacenterContainer}/${item.properties.displayName}`
+        let getLabel = item => item.properties.displayName
         return getLabel(a).localeCompare(getLabel(b))
       })
       return vms

+ 3 - 1
src/sources/InstanceSource.js

@@ -83,8 +83,9 @@ class InstanceSource {
     quietError?: boolean,
     env?: any,
     cache?: ?boolean,
+    skipLog?: ?boolean,
   }): Promise<{ instance: Instance, reqId: number }> {
-    let { endpointId, instanceName, targetProvider, reqId, quietError, env, cache } = opts
+    let { endpointId, instanceName, targetProvider, reqId, quietError, env, cache, skipLog } = opts
     let url = `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances/${btoa(instanceName)}`
     if (env) {
       url += `?env=${btoa(JSON.stringify(env))}`
@@ -94,6 +95,7 @@ class InstanceSource {
       cancelId: `instanceDetail-${reqId}`,
       quietError,
       cache,
+      skipLog,
     })
 
     let instanceInfoParser = (targetProvider && InstanceInfoPlugin[targetProvider]) || InstanceInfoPlugin.default

+ 2 - 2
src/stores/AzureStore.js

@@ -152,9 +152,9 @@ class AzureStore {
     AzureLocalStorage.setLocalData(this.localData)
   }
 
-  @action authenticate(username: string, password: string): Promise<void> {
+  @action authenticate(connectionInfo: any): Promise<void> {
     this.authenticating = true
-    return AzureSource.authenticate(username, password).then(() => {
+    return AzureSource.authenticate(connectionInfo).then(() => {
       this.authenticating = false
     }).catch(() => {
       this.authenticating = false

+ 46 - 7
src/stores/InstanceStore.js

@@ -35,7 +35,7 @@ class InstanceStore {
   @observable searchNotFound = false
   @observable reloading = false
   @observable instancesDetails: Instance[] = []
-  @observable loadingInstancesDetails = true
+  @observable loadingInstancesDetails = false
   @observable instancesDetailsCount = 0
   @observable instancesDetailsRemaining = 0
   @observable searchText = ''
@@ -250,15 +250,54 @@ class InstanceStore {
     }
   }
 
+  @action async addInstanceDetails(opts: {
+    endpointId: string,
+    instanceInfo: Instance,
+    cache?: boolean,
+    quietError?: boolean,
+    env?: any,
+    targetProvider: string,
+  }) {
+    let { endpointId, instanceInfo, cache, quietError, env, targetProvider } = opts
+    this.loadingInstancesDetails = true
+    let resp: { instance: Instance, reqId: number } =
+      await InstanceSource.loadInstanceDetails({
+        endpointId,
+        instanceName: instanceInfo.instance_name || instanceInfo.name,
+        targetProvider,
+        reqId: this.reqId,
+        quietError,
+        env,
+        cache,
+      })
+
+    runInAction(() => {
+      this.loadingInstancesDetails = false
+      if (this.instancesDetails.find(i => i.id === resp.instance.id)) {
+        this.instancesDetails = this.instancesDetails.filter(i => i.id !== resp.instance.id)
+      }
+      this.instancesDetails = [
+        ...this.instancesDetails,
+        resp.instance,
+      ]
+      this.instancesDetails.sort((a, b) => (a.instance_name || a.name).localeCompare((b.instance_name || b.name)))
+    })
+  }
+
+  @action removeInstanceDetails(instance: Instance) {
+    this.instancesDetails = this.instancesDetails.filter(i => i.id !== instance.id)
+  }
+
   @action async loadInstancesDetails(opts: {
     endpointId: string,
     instancesInfo: Instance[],
     cache?: boolean,
     quietError?: boolean,
+    skipLog?: boolean,
     env?: any,
     targetProvider: string,
   }): Promise<void> {
-    let { endpointId, instancesInfo, cache, quietError, env, targetProvider } = opts
+    let { endpointId, instancesInfo, cache, quietError, env, targetProvider, skipLog } = opts
     // Use reqId to be able to uniquely identify the request so all but the latest request can be igonred and canceled
     this.reqId = !this.reqId ? 1 : this.reqId + 1
     InstanceSource.cancelInstancesDetailsRequests(this.reqId - 1)
@@ -266,9 +305,11 @@ class InstanceStore {
     instancesInfo.sort((a, b) => (a.instance_name || a.name).localeCompare(b.instance_name || b.name))
 
     let count = instancesInfo.length
+    if (count === 0) {
+      return
+    }
     this.loadingInstancesDetails = true
     this.instancesDetails = []
-    this.loadingInstancesDetails = true
     this.instancesDetailsCount = count
     this.instancesDetailsRemaining = count
 
@@ -284,6 +325,7 @@ class InstanceStore {
               quietError,
               env,
               cache,
+              skipLog,
             })
           if (resp.reqId !== this.reqId) {
             return
@@ -296,16 +338,13 @@ class InstanceStore {
             if (this.instancesDetails.find(i => i.id === resp.instance.id)) {
               this.instancesDetails = this.instancesDetails.filter(i => i.id !== resp.instance.id)
             }
-          })
-
-          runInAction(() => {
             this.instancesDetails = [
               ...this.instancesDetails,
               resp.instance,
             ]
-            this.instancesDetails.sort((a, b) => (a.instance_name || a.name).localeCompare((b.instance_name || b.name)))
           })
           if (this.instancesDetailsRemaining === 0) {
+            this.instancesDetails.sort((a, b) => (a.instance_name || a.name).localeCompare((b.instance_name || b.name)))
             resolve()
           }
         } catch (err) {

+ 3 - 4
src/types/Assessment.js

@@ -42,11 +42,10 @@ export type VmItem = {
         recommendedDiskType: string,
       },
     },
-    datacenterContainer: string,
-    datacenterManagementServer: string,
-    datacenterMachineId: string,
+    datacenterManagementServerName: string,
+    datacenterMachineArmId: string,
     displayName: string,
-    operatingSystem: string,
+    operatingSystemName: string,
   },
 }
 

+ 2 - 1
src/utils/ApiCaller.js

@@ -164,7 +164,8 @@ class ApiCaller {
             })
           }
 
-          if (error.request.responseURL.indexOf('/proxy/') === -1) {
+          if (error.request.responseURL.indexOf('/proxy/') === -1
+            && error.request.responseURL.indexOf('/azure-login') === -1) {
             redirect(error.response.status)
           }