You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2020/05/22 21:20:54 UTC

[cloudstack-primate] branch master updated: compute: VM deployment wizard fixes (#307)

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git


The following commit(s) were added to refs/heads/master by this push:
     new c9de1ff  compute: VM deployment wizard fixes (#307)
c9de1ff is described below

commit c9de1ff89b145415f0a46deaa03bf41111ac6299
Author: Hoang Nguyen <ho...@unitech.vn>
AuthorDate: Sat May 23 04:20:43 2020 +0700

    compute: VM deployment wizard fixes (#307)
    
    - Fix reactive changes on zone selection
    - Template filter on left side of search box
    - Allow group name
    - Ability to add network while deploying VM
    - Show password if VM deployment returns password
    
    Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
    Co-authored-by: Rohit Yadav <ro...@shapeblue.com>
---
 src/components/view/InfoCard.vue                   |  14 +--
 src/locales/en.json                                |   5 +-
 src/views/compute/CreateKubernetesCluster.vue      |   2 +-
 src/views/compute/DeployVM.vue                     | 119 +++++++++++----------
 .../compute/wizard/ComputeOfferingSelection.vue    |   6 +-
 src/views/compute/wizard/NetworkConfiguration.vue  |  23 +++-
 src/views/compute/wizard/NetworkSelection.vue      |  42 ++++++--
 src/views/compute/wizard/TemplateIsoSelection.vue  |  46 +++++---
 src/views/network/CreateIsolatedNetworkForm.vue    |   2 +-
 src/views/network/CreateL2NetworkForm.vue          |   2 +-
 src/views/network/CreateSharedNetworkForm.vue      |   4 +-
 11 files changed, 167 insertions(+), 98 deletions(-)

diff --git a/src/components/view/InfoCard.vue b/src/components/view/InfoCard.vue
index 4e21f89..befda64 100644
--- a/src/components/view/InfoCard.vue
+++ b/src/components/view/InfoCard.vue
@@ -344,6 +344,13 @@
             <span v-if="index + 1 < resource.affinitygroup.length">, </span>
           </span>
         </div>
+        <div class="resource-detail-item" v-if="resource.templateid">
+          <div class="resource-detail-item__label">{{ $t('templatename') }}</div>
+          <div class="resource-detail-item__details">
+            <a-icon type="picture" />
+            <router-link :to="{ path: '/template/' + resource.templateid }">{{ resource.templatename || resource.templateid }} </router-link>
+          </div>
+        </div>
         <div class="resource-detail-item" v-if="resource.serviceofferingname && resource.serviceofferingid">
           <div class="resource-detail-item__label">{{ $t('serviceofferingname') }}</div>
           <div class="resource-detail-item__details">
@@ -352,13 +359,6 @@
             <span v-else>{{ resource.serviceofferingname || resource.serviceofferingid }}</span>
           </div>
         </div>
-        <div class="resource-detail-item" v-if="resource.templateid">
-          <div class="resource-detail-item__label">{{ $t('templatename') }}</div>
-          <div class="resource-detail-item__details">
-            <a-icon type="picture" />
-            <router-link :to="{ path: '/template/' + resource.templateid }">{{ resource.templatename || resource.templateid }} </router-link>
-          </div>
-        </div>
         <div class="resource-detail-item" v-if="resource.diskofferingname && resource.diskofferingid">
           <div class="resource-detail-item__label">{{ $t('diskoffering') }}</div>
           <div class="resource-detail-item__details">
diff --git a/src/locales/en.json b/src/locales/en.json
index 3134b00..0faabc3 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -481,6 +481,7 @@
 "label.add.ldap.list.users": "List LDAP users",
 "label.add.list.name":"ACL List Name",
 "label.add.netScaler.device": "Add Netscaler device",
+"label.add.network":"Add Network",
 "label.add.network.offering": "Add network offering",
 "label.add.new.gateway": "Add new gateway",
 "label.add.new.tier": "Add new tier",
@@ -1308,5 +1309,7 @@
 "writeback": "Write-back disk caching",
 "writethrough": "Write-through",
 "none": "None",
-"maxcpunumber": "Max CPU Cores"
+"maxcpunumber": "Max CPU Cores",
+"message.template.iso": "Please select a template or ISO to continue",
+"label.launch.vm": "Launch Virtual Machine"
 }
diff --git a/src/views/compute/CreateKubernetesCluster.vue b/src/views/compute/CreateKubernetesCluster.vue
index 15b9237..e306d72 100644
--- a/src/views/compute/CreateKubernetesCluster.vue
+++ b/src/views/compute/CreateKubernetesCluster.vue
@@ -49,7 +49,7 @@
             }"
             :loading="zoneLoading"
             :placeholder="apiParams.zoneid.description"
-            @change="val => { this.handleZoneChanged(this.zones[val]) }">
+            @change="val => { this.handleZoneChange(this.zones[val]) }">
             <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
               {{ opt.name || opt.description }}
             </a-select-option>
diff --git a/src/views/compute/DeployVM.vue b/src/views/compute/DeployVM.vue
index 4459141..4943cbb 100644
--- a/src/views/compute/DeployVM.vue
+++ b/src/views/compute/DeployVM.vue
@@ -72,11 +72,7 @@
                       ></a-select>
                     </a-form-item>
                     <a-form-item :label="this.$t('group')">
-                      <a-select
-                        v-decorator="['group']"
-                        :options="groupsSelectOptions"
-                        :loading="loading.groups"
-                      ></a-select>
+                      <a-input v-decorator="['group']" />
                     </a-form-item>
                     <a-form-item :label="this.$t('keyboard')">
                       <a-select
@@ -122,8 +118,7 @@
                           :selected="tabKey"
                           :loading="loading.isos"
                           :preFillContent="dataPreFill"
-                          @update-template-iso="updateFieldValue"
-                        ></template-iso-selection>
+                          @update-template-iso="updateFieldValue" />
                         <a-form-item :label="this.$t('hypervisor')">
                           <a-select
                             v-decorator="['hypervisor', {
@@ -133,9 +128,7 @@
                               rules: [{ required: true, message: 'Please select option' }]
                             }]"
                             :options="hypervisorSelectOptions"
-                            @change="value => this.hypervisor = value"
-                          >
-                          </a-select>
+                            @change="value => this.hypervisor = value" />
                         </a-form-item>
                       </p>
                     </a-card>
@@ -318,8 +311,8 @@ import { mixin, mixinDevice } from '@/utils/mixin.js'
 import store from '@/store'
 
 import InfoCard from '@/components/view/InfoCard'
-import ComputeOfferingSelection from './wizard/ComputeOfferingSelection'
-import ComputeSelection from './wizard/ComputeSelection'
+import ComputeOfferingSelection from '@views/compute/wizard/ComputeOfferingSelection'
+import ComputeSelection from '@views/compute/wizard/ComputeSelection'
 import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection'
 import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection'
 import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection'
@@ -424,6 +417,7 @@ export default {
       initDataConfig: {},
       defaultNetwork: '',
       networkConfig: [],
+      dataNetworkCreated: [],
       tabList: [
         {
           key: 'templateid',
@@ -548,14 +542,6 @@ export default {
             type: 'Routing'
           },
           field: 'hostid'
-        },
-        groups: {
-          list: 'listInstanceGroups',
-          options: {
-            listall: false
-          },
-          isLoad: true,
-          field: 'group'
         }
       }
     },
@@ -609,14 +595,6 @@ export default {
           value: keyboard.id
         }
       })
-    },
-    groupsSelectOptions () {
-      return this.options.groups.map((group) => {
-        return {
-          label: group.name,
-          value: group.id
-        }
-      })
     }
   },
   watch: {
@@ -854,10 +832,23 @@ export default {
     handleSubmit (e) {
       console.log('wizard submit')
       e.preventDefault()
-      this.form.validateFields((err, values) => {
+      this.form.validateFields(async (err, values) => {
         if (err) {
           return
         }
+
+        if (!values.templateid && !values.isoid) {
+          this.$notification.error({
+            message: 'Request Failed',
+            description: this.$t('message.template.iso')
+          })
+          return
+        }
+
+        this.loading.deploy = true
+
+        let networkIds = []
+
         const deployVmData = {}
         // step 1 : select zone
         deployVmData.zoneid = values.zoneid
@@ -902,15 +893,30 @@ export default {
         // step 5: select an affinity group
         deployVmData.affinitygroupids = (values.affinitygroupids || []).join(',')
         // step 6: select network
-        if (values.networkids && values.networkids.length > 0) {
-          for (let i = 0; i < values.networkids.length; i++) {
-            deployVmData['iptonetworklist[' + i + '].networkid'] = values.networkids[i]
-            if (this.networkConfig.length > 0) {
-              const networkConfig = this.networkConfig.filter((item) => item.key === values.networkids[i])
-              if (networkConfig && networkConfig.length > 0) {
-                deployVmData['iptonetworklist[' + i + '].ip'] = networkConfig[0].ipAddress ? networkConfig[0].ipAddress : undefined
-                deployVmData['iptonetworklist[' + i + '].mac'] = networkConfig[0].macAddress ? networkConfig[0].macAddress : undefined
+        const arrNetwork = []
+        networkIds = values.networkids
+        if (networkIds.length > 0) {
+          for (let i = 0; i < networkIds.length; i++) {
+            if (networkIds[i] === this.defaultNetwork) {
+              const ipToNetwork = {
+                networkid: this.defaultNetwork
+              }
+              arrNetwork.unshift(ipToNetwork)
+            } else {
+              const ipToNetwork = {
+                networkid: networkIds[i]
               }
+              arrNetwork.push(ipToNetwork)
+            }
+          }
+        }
+        for (let j = 0; j < arrNetwork.length; j++) {
+          deployVmData['iptonetworklist[' + j + '].networkid'] = arrNetwork[j].networkid
+          if (this.networkConfig.length > 0) {
+            const networkConfig = this.networkConfig.filter((item) => item.key === arrNetwork[j].networkid)
+            if (networkConfig && networkConfig.length > 0) {
+              deployVmData['iptonetworklist[' + j + '].ip'] = networkConfig[0].ipAddress ? networkConfig[0].ipAddress : undefined
+              deployVmData['iptonetworklist[' + j + '].mac'] = networkConfig[0].macAddress ? networkConfig[0].macAddress : undefined
             }
           }
         }
@@ -918,31 +924,34 @@ export default {
         deployVmData.keypair = values.keypair
         deployVmData.name = values.name
         deployVmData.displayname = values.name
-        const title = this.$t('Launch Virtual Machine')
-        const description = deployVmData.name ? deployVmData.name : values.zoneid
-        this.loading.deploy = true
+        const title = this.$t('label.launch.vm')
+        const description = values.name || ''
+        const password = this.$t('password')
         api('deployVirtualMachine', deployVmData).then(response => {
           const jobId = response.deployvirtualmachineresponse.jobid
           if (jobId) {
             this.$pollJob({
               jobId,
               successMethod: result => {
-                let successDescription = ''
-                if (result.jobresult.virtualmachine.name) {
-                  successDescription = result.jobresult.virtualmachine.name
-                } else {
-                  successDescription = result.jobresult.virtualmachine.id
+                const vm = result.jobresult.virtualmachine
+                const name = vm.displayname || vm.name || vm.id
+                if (vm.password) {
+                  this.$notification.success({
+                    message: password + ' for ' + name,
+                    description: vm.password,
+                    duration: 0
+                  })
                 }
-                this.$store.dispatch('AddAsyncJob', {
-                  title: title,
-                  jobid: jobId,
-                  description: successDescription,
-                  status: 'progress'
-                })
               },
-              loadingMessage: `${title} in progress for ${description}`,
+              loadingMessage: `${title} in progress`,
               catchMessage: 'Error encountered while fetching async job result'
             })
+            this.$store.dispatch('AddAsyncJob', {
+              title: title,
+              jobid: jobId,
+              description: description,
+              status: 'progress'
+            })
           }
           this.$router.back()
         }).catch(error => {
@@ -1036,12 +1045,12 @@ export default {
         })
       })
     },
-    fetchAllTemplates (filterKey) {
+    fetchAllTemplates (filterKeys) {
       const promises = []
       this.options.templates = []
       this.loading.templates = true
       this.templateFilter.forEach((filter) => {
-        if (filterKey && filterKey !== filter) {
+        if (filterKeys && !filterKeys.includes(filter)) {
           return true
         }
         promises.push(this.fetchTemplates(filter))
@@ -1095,7 +1104,7 @@ export default {
       this.tabKey = 'templateid'
       _.each(this.params, (param, name) => {
         if (!('isLoad' in param) || param.isLoad) {
-          this.fetchOptions(param, name, ['zones', 'groups'])
+          this.fetchOptions(param, name, ['zones'])
         }
       })
       this.fetchAllTemplates()
diff --git a/src/views/compute/wizard/ComputeOfferingSelection.vue b/src/views/compute/wizard/ComputeOfferingSelection.vue
index 4403ee4..6f2304d 100644
--- a/src/views/compute/wizard/ComputeOfferingSelection.vue
+++ b/src/views/compute/wizard/ComputeOfferingSelection.vue
@@ -137,8 +137,10 @@ export default {
           this.selectedRowKeys = [this.preFillContent.computeofferingid]
           this.$emit('select-compute-item', this.preFillContent.computeofferingid)
         } else {
-          this.selectedRowKeys = []
-          this.$emit('select-compute-item', null)
+          if (this.computeItems && this.computeItems.length > 0) {
+            this.selectedRowKeys = [this.computeItems[0].id]
+            this.$emit('select-compute-item', this.computeItems[0].id)
+          }
         }
       }
     }
diff --git a/src/views/compute/wizard/NetworkConfiguration.vue b/src/views/compute/wizard/NetworkConfiguration.vue
index af20568..d2c0a4d 100644
--- a/src/views/compute/wizard/NetworkConfiguration.vue
+++ b/src/views/compute/wizard/NetworkConfiguration.vue
@@ -19,7 +19,7 @@
   <a-table
     :columns="columns"
     :dataSource="dataItems"
-    :pagination="{showSizeChanger: true}"
+    :pagination="false"
     :rowSelection="rowSelection"
     :rowKey="record => record.id"
     size="middle"
@@ -64,7 +64,7 @@ export default {
         {
           dataIndex: 'name',
           title: this.$t('defaultNetwork'),
-          width: '40%'
+          width: '30%'
         },
         {
           dataIndex: 'ip',
@@ -88,8 +88,10 @@ export default {
   },
   created () {
     this.dataItems = this.items
-    this.selectedRowKeys = [this.dataItems[0].id]
-    this.$emit('select-default-network-item', this.selectedRowKeys)
+    if (this.dataItems.length > 0) {
+      this.selectedRowKeys = [this.dataItems[0].id]
+      this.$emit('select-default-network-item', this.dataItems[0].id)
+    }
   },
   computed: {
     rowSelection () {
@@ -112,6 +114,7 @@ export default {
         const keyEx = this.dataItems.filter((item) => this.selectedRowKeys.includes(item.id))
         if (!keyEx || keyEx.length === 0) {
           this.selectedRowKeys = [this.dataItems[0].id]
+          this.$emit('select-default-network-item', this.dataItems[0].id)
         }
       }
     }
@@ -122,7 +125,8 @@ export default {
       this.$emit('select-default-network-item', value[0])
     },
     updateNetworkData (name, key, value) {
-      if (this.networks.length === 0) {
+      const index = this.networks.findIndex(item => item.key === key)
+      if (index === -1) {
         const networkItem = {}
         networkItem.key = key
         networkItem[name] = value
@@ -137,6 +141,15 @@ export default {
         }
       })
       this.$emit('update-network-config', this.networks)
+    },
+    removeItem (id) {
+      this.dataItems = this.dataItems.filter(item => item.id !== id)
+      if (this.selectedRowKeys.includes(id)) {
+        if (this.dataItems && this.dataItems.length > 0) {
+          this.selectedRowKeys = [this.dataItems[0].id]
+          this.$emit('select-default-network-item', this.dataItems[0].id)
+        }
+      }
     }
   }
 }
diff --git a/src/views/compute/wizard/NetworkSelection.vue b/src/views/compute/wizard/NetworkSelection.vue
index 74678b0..a80c891 100644
--- a/src/views/compute/wizard/NetworkSelection.vue
+++ b/src/views/compute/wizard/NetworkSelection.vue
@@ -18,17 +18,13 @@
 <template>
   <div>
     <a-input-search
-      style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+      style="width: 25vw; float: right; margin-bottom: 10px; z-index: 8"
       placeholder="Search"
       v-model="filter"
       @search="handleSearch" />
-    <a-tooltip
-      arrowPointAtCenter
-      placement="bottomRight">
-      <template slot="title">
-        {{ $t('addNewNetworks') }}
-      </template>
-    </a-tooltip>
+    <a-button type="primary" @click="showCreateForm = true" style="float: right; margin-right: 5px; z-index: 8">
+      {{ $t('label.add.network') }}
+    </a-button>
     <a-table
       :loading="loading"
       :columns="columns"
@@ -55,6 +51,20 @@
         </a-list-item>
       </a-list>
     </a-table>
+    <a-modal
+      :visible="showCreateForm"
+      :title="$t('label.add.network')"
+      :closable="true"
+      :footer="null"
+      @cancel="showCreateForm = false"
+      centered
+      width="auto">
+      <create-network
+        :resource="{}"
+        @refresh-data="handleSearch"
+        @close-action="showCreateForm = false"
+      />
+    </a-modal>
   </div>
 </template>
 
@@ -62,9 +72,13 @@
 import _ from 'lodash'
 import { api } from '@/api'
 import store from '@/store'
+import CreateNetwork from '@/views/network/CreateNetwork'
 
 export default {
   name: 'NetworkSelection',
+  components: {
+    CreateNetwork
+  },
   props: {
     items: {
       type: Array,
@@ -96,7 +110,8 @@ export default {
       networkOffering: {
         loading: false,
         opts: []
-      }
+      },
+      showCreateForm: false
     }
   },
   computed: {
@@ -173,8 +188,13 @@ export default {
           this.selectedRowKeys = this.preFillContent.networkids
           this.$emit('select-network-item', this.preFillContent.networkids)
         } else {
-          this.selectedRowKeys = []
-          this.$emit('select-network-item', null)
+          if (this.items && this.items.length > 0) {
+            this.selectedRowKeys = [this.items[0].id]
+            this.$emit('select-network-item', this.selectedRowKeys)
+          } else {
+            this.selectedRowKeys = []
+            this.$emit('select-network-item', [])
+          }
         }
       }
     }
diff --git a/src/views/compute/wizard/TemplateIsoSelection.vue b/src/views/compute/wizard/TemplateIsoSelection.vue
index 7edef3c..bffc9b5 100644
--- a/src/views/compute/wizard/TemplateIsoSelection.vue
+++ b/src/views/compute/wizard/TemplateIsoSelection.vue
@@ -37,6 +37,7 @@
               <a-form-item :label="$t('filter')">
                 <a-select
                   allowClear
+                  mode="multiple"
                   v-decorator="['filter']">
                   <a-select-option
                     v-for="(opt) in filterOpts"
@@ -244,21 +245,23 @@ export default {
         if (err) {
           return
         }
-        if (this.inputDecorator === 'template') {
-          this.vmFetchTemplates(values.filter)
-        } else {
-          this.vmFetchIsos(values.filter)
-        }
+        const filtered = values.filter || []
+        this.filter = ''
+        filtered.map(item => {
+          if (this.filter.length === 0) {
+            this.filter += 'is:' + item
+          } else {
+            this.filter += '; is:' + item
+          }
+        })
+        this.filterDataSource(this.filter)
       })
     },
     onClear () {
       const field = { filter: undefined }
       this.form.setFieldsValue(field)
-      if (this.inputDecorator === 'template') {
-        this.vmFetchTemplates()
-      } else {
-        this.vmFetchIsos()
-      }
+      this.filter = ''
+      this.filterDataSource('')
     },
     changeOsName (value) {
       this.osType = value
@@ -288,8 +291,27 @@ export default {
   }
 
   .filter-group {
+    /deep/.ant-input-affix-wrapper {
+      float: right;
+      width: calc(100% - 32px);
+
+      .ant-input {
+        border-radius: 4px;
+        border-top-left-radius: 0;
+        border-bottom-left-radius: 0;
+      }
+    }
+
     /deep/.ant-input-group-addon {
-      padding: 0 5px;
+      float: left;
+      width: 32px;
+      height: 32px;
+      border-radius: 4px;
+      border-right: 0;
+      border-left: 1px solid #d9d9d9;
+      border-top-right-radius: 0;
+      border-bottom-right-radius: 0;
+      padding: 0 0 0 1px;
     }
 
     &-button {
@@ -301,7 +323,7 @@ export default {
     &-button {
       position: relative;
       display: block;
-      min-height: 25px;
+      min-height: 30px;
 
       &-clear {
         position: absolute;
diff --git a/src/views/network/CreateIsolatedNetworkForm.vue b/src/views/network/CreateIsolatedNetworkForm.vue
index 15ebf19..a4c40a6 100644
--- a/src/views/network/CreateIsolatedNetworkForm.vue
+++ b/src/views/network/CreateIsolatedNetworkForm.vue
@@ -54,7 +54,7 @@
               }"
               :loading="zoneLoading"
               :placeholder="this.$t('.zoneid')"
-              @change="val => { this.handleZoneChanged(this.zones[val]) }">
+              @change="val => { this.handleZoneChange(this.zones[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
                 {{ opt.name || opt.description }}
               </a-select-option>
diff --git a/src/views/network/CreateL2NetworkForm.vue b/src/views/network/CreateL2NetworkForm.vue
index 4eff20a..0489aa7 100644
--- a/src/views/network/CreateL2NetworkForm.vue
+++ b/src/views/network/CreateL2NetworkForm.vue
@@ -54,7 +54,7 @@
               }"
               :loading="zoneLoading"
               :placeholder="this.$t('zoneid')"
-              @change="val => { this.handleZoneChanged(this.zones[val]) }">
+              @change="val => { this.handleZoneChange(this.zones[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
                 {{ opt.name || opt.description }}
               </a-select-option>
diff --git a/src/views/network/CreateSharedNetworkForm.vue b/src/views/network/CreateSharedNetworkForm.vue
index 2303fee..f47a008 100644
--- a/src/views/network/CreateSharedNetworkForm.vue
+++ b/src/views/network/CreateSharedNetworkForm.vue
@@ -54,7 +54,7 @@
               }"
               :loading="zoneLoading"
               :placeholder="this.$t('zoneid')"
-              @change="val => { this.handleZoneChanged(this.zones[val]) }">
+              @change="val => { this.handleZoneChange(this.zones[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
                 {{ opt.name || opt.description }}
               </a-select-option>
@@ -70,7 +70,7 @@
               }"
               :loading="zoneLoading"
               :placeholder="this.$t('physicalnetworkid')"
-              @change="val => { this.handleZoneChanged(this.formPhysicalNetworks[val]) }">
+              @change="val => { this.handleZoneChange(this.formPhysicalNetworks[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.formPhysicalNetworks" :key="optIndex">
                 {{ opt.name || opt.description }}
               </a-select-option>