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/03/25 21:18:49 UTC

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

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 86845be  compute: VM deployment wizard (#146)
86845be is described below

commit 86845be4813e9bd50b6278bd0d02deb5c10fd411
Author: Hoang Nguyen <ho...@unitech.vn>
AuthorDate: Thu Mar 26 04:18:39 2020 +0700

    compute: VM deployment wizard (#146)
    
    A mostly functional vm deployment wizard.
    
    Signed-off-by: Abhishek Kumar <ab...@gmail.com>
    Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
---
 src/locales/en.json                                |   2 +-
 src/utils/device.js                                |   7 +-
 src/views/AutogenView.vue                          |   2 +-
 src/views/compute/DeployVM.vue                     | 806 +++++++++++++++++----
 src/views/compute/KubernetesServiceTab.vue         |   2 +-
 .../compute/wizard/AffinityGroupSelection.vue      |  51 +-
 src/views/compute/wizard/ComputeSelection.vue      |  62 +-
 src/views/compute/wizard/DiskOfferingSelection.vue | 108 ++-
 src/views/compute/wizard/DiskSizeSelection.vue     |  17 +-
 src/views/compute/wizard/NetworkConfiguration.vue  |  65 +-
 src/views/compute/wizard/NetworkSelection.vue      |  76 +-
 src/views/compute/wizard/NetworksCreation.vue      | 150 ----
 src/views/compute/wizard/SshKeyPairSelection.vue   |  80 +-
 src/views/compute/wizard/TemplateIsoRadioGroup.vue | 136 +++-
 src/views/compute/wizard/TemplateIsoSelection.vue  | 161 ++--
 src/views/iam/SSLCertificateTab.vue                |   2 +-
 src/views/image/IsoZones.vue                       |   2 +-
 src/views/image/TemplateZones.vue                  |   2 +-
 src/views/network/VpcTab.vue                       |   2 +-
 19 files changed, 1242 insertions(+), 491 deletions(-)

diff --git a/src/locales/en.json b/src/locales/en.json
index 6511211..d4ddbff 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -1103,7 +1103,7 @@
 "sshKeyPairs": "SSH keypairs",
 "wednesday": "Wednesday",
 "noselect": "No thanks",
-"groupname": "Add to group",
+"group": "Group",
 "keyboard": "Keyboard language",
 "userdata": "Userdata",
 "label.back": "Back",
diff --git a/src/utils/device.js b/src/utils/device.js
index 731270d..ce6deab 100644
--- a/src/utils/device.js
+++ b/src/utils/device.js
@@ -42,9 +42,8 @@ export const deviceEnquire = function (callback) {
     }
   }
 
-  // screen and (max-width: 1087.99px)
   enquireJs
-    .register('screen and (max-width: 576px)', matchMobile)
-    .register('screen and (min-width: 576px) and (max-width: 1280px)', matchTablet)
-    .register('screen and (min-width: 1281px)', matchDesktop)
+    .register('screen and (max-width: 800px)', matchMobile)
+    .register('screen and (min-width: 800px) and (max-width: 1366px)', matchTablet)
+    .register('screen and (min-width: 1367px)', matchDesktop)
 }
diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue
index 8887a5e..871c380 100644
--- a/src/views/AutogenView.vue
+++ b/src/views/AutogenView.vue
@@ -235,7 +235,7 @@
         :pageSize="pageSize"
         :total="itemCount"
         :showTotal="total => `Total ${total} items`"
-        :pageSizeOptions="['10', '20', '40', '80', '100']"
+        :pageSizeOptions="['10', '20', '40', '80', '100', '500']"
         @change="changePage"
         @showSizeChange="changePageSize"
         showSizeChanger
diff --git a/src/views/compute/DeployVM.vue b/src/views/compute/DeployVM.vue
index eb0a5f7..f1a4c9e 100644
--- a/src/views/compute/DeployVM.vue
+++ b/src/views/compute/DeployVM.vue
@@ -25,131 +25,216 @@
             @submit="handleSubmit"
             layout="vertical"
           >
-            <a-collapse
-              :accordion="false"
-              :bordered="false"
-              defaultActiveKey="basic"
-            >
-              <a-collapse-panel :header="this.$t('BasicSetup')" key="basic">
-                <a-form-item :label="this.$t('name')">
-                  <a-input
-                    v-decorator="['name']"
-                    :placeholder="this.$t('vm.name.description')"
-                  />
-                </a-form-item>
-
-                <a-form-item :label="this.$t('zoneid')">
-                  <a-select
-                    v-decorator="['zoneid', {
-                      rules: [{ required: true, message: 'Please select option' }]
-                    }]"
-                    :placeholder="this.$t('vm.zone.description')"
-                    :options="zoneSelectOptions"
-                    @change="onSelectZoneId"
-                  ></a-select>
-                </a-form-item>
-              </a-collapse-panel>
-
-              <a-collapse-panel :header="this.$t('templateIso')" key="templates-isos">
-                <a-collapse
-                  :accordion="true"
-                  defaultActiveKey="templates"
-                  @change="onTemplatesIsosCollapseChange"
-                >
-                  <a-collapse-panel :header="this.$t('Templates')" key="templates">
-                    <template-iso-selection
-                      input-decorator="templateid"
-                      :items="options.templates"
-                    ></template-iso-selection>
+            <a-steps direction="vertical" size="small">
+              <a-step :title="this.$t('details')" status="process">
+                <template slot="description">
+                  <div style="margin-top: 15px">
+                    <a-form-item :label="this.$t('name')">
+                      <a-input
+                        v-decorator="['name']"
+                      />
+                    </a-form-item>
+                    <a-form-item :label="this.$t('zoneid')">
+                      <a-select
+                        v-decorator="['zoneid', {
+                          rules: [{ required: true, message: 'Please select option' }]
+                        }]"
+                        :options="zoneSelectOptions"
+                        @change="onSelectZoneId"
+                        :loading="loading.zones"
+                      ></a-select>
+                    </a-form-item>
+                    <a-form-item :label="this.$t('podId')">
+                      <a-select
+                        v-decorator="['podid']"
+                        :options="podSelectOptions"
+                        :loading="loading.pods"
+                      ></a-select>
+                    </a-form-item>
+                    <a-form-item :label="this.$t('clusterid')">
+                      <a-select
+                        v-decorator="['clusterid']"
+                        :options="clusterSelectOptions"
+                        :loading="loading.clusters"
+                      ></a-select>
+                    </a-form-item>
+                    <a-form-item :label="this.$t('hostId')">
+                      <a-select
+                        v-decorator="['hostid']"
+                        :options="hostSelectOptions"
+                        :loading="loading.hosts"
+                      ></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-form-item>
+                    <a-form-item :label="this.$t('keyboard')">
+                      <a-select
+                        v-decorator="['keyboard']"
+                        :options="keyboardSelectOptions"
+                      ></a-select>
+                    </a-form-item>
+                    <a-form-item :label="this.$t('userdata')">
+                      <a-textarea
+                        v-decorator="['userdata']">
+                      </a-textarea>
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="this.$t('templateIso')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template slot="description">
+                  <div v-if="zoneSelected" style="margin-top: 15px">
+                    <a-card
+                      :tabList="tabList"
+                      :activeTabKey="tabKey"
+                      @tabChange="key => onTabChange(key, 'tabKey')">
+                      <p v-if="tabKey === 'templateid'">
+                        <template-iso-selection
+                          input-decorator="templateid"
+                          :items="options.templates"
+                          :selected="tabKey"
+                          :loading="loading.templates"
+                          @update-template-iso="updateFieldValue"
+                        ></template-iso-selection>
+                        <disk-size-selection
+                          input-decorator="rootdisksize"
+                          @update-disk-size="updateFieldValue"/>
+                      </p>
+                      <p v-else>
+                        <template-iso-selection
+                          input-decorator="isoid"
+                          :items="options.isos"
+                          :selected="tabKey"
+                          :loading="loading.isos"
+                          @update-template-iso="updateFieldValue"
+                        ></template-iso-selection>
+                      </p>
+                    </a-card>
+                    <a-form-item class="form-item-hidden">
+                      <a-input v-decorator="['templateid']"/>
+                    </a-form-item>
+                    <a-form-item class="form-item-hidden">
+                      <a-input v-decorator="['isoid']"/>
+                    </a-form-item>
+                    <a-form-item class="form-item-hidden">
+                      <a-input v-decorator="['rootdisksize']"/>
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="this.$t('serviceOfferingId')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template slot="description">
+                  <div v-if="zoneSelected">
+                    <compute-selection
+                      :compute-items="options.serviceOfferings"
+                      :value="serviceOffering ? serviceOffering.id : ''"
+                      :loading="loading.serviceOfferings"
+                      @select-compute-item="($event) => updateComputeOffering($event)"
+                      @handle-search-filter="($event) => handleSearchFilter('serviceOfferings', $event)"
+                    ></compute-selection>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="this.$t('diskofferingid')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template slot="description">
+                  <div v-if="zoneSelected">
+                    <disk-offering-selection
+                      :items="options.diskOfferings"
+                      :value="diskOffering ? diskOffering.id : ''"
+                      :loading="loading.diskOfferings"
+                      @select-disk-offering-item="($event) => updateDiskOffering($event)"
+                      @handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
+                    ></disk-offering-selection>
                     <disk-size-selection
-                      input-decorator="rootdisksize"
-                    ></disk-size-selection>
-                  </a-collapse-panel>
-
-                  <a-collapse-panel :header="this.$t('ISOs')" key="isos">
-                    <template-iso-selection
-                      input-decorator="isoid"
-                      :items="options.isos"
-                    ></template-iso-selection>
-                  </a-collapse-panel>
-                </a-collapse>
-              </a-collapse-panel>
-
-              <a-collapse-panel :header="this.$t('serviceOfferingId')" key="compute">
-                <compute-selection
-                  :compute-items="options.serviceOfferings"
-                  :value="serviceOffering ? serviceOffering.id : ''"
-                  @select-compute-item="($event) => updateComputeOffering($event)"
-                ></compute-selection>
-              </a-collapse-panel>
-
-              <a-collapse-panel :header="this.$t('diskOfferingId')" key="disk">
-                <disk-offering-selection
-                  :items="options.diskOfferings"
-                  :value="diskOffering ? diskOffering.id : ''"
-                  @select-disk-offering-item="($event) => updateDiskOffering($event)"
-                ></disk-offering-selection>
-                <disk-size-selection
-                  v-if="diskOffering && diskOffering.iscustomized"
-                  input-decorator="size"
-                ></disk-size-selection>
-              </a-collapse-panel>
-
-              <a-collapse-panel :header="this.$t('Affinity Groups')" key="affinity">
-                <affinity-group-selection
-                  :items="options.affinityGroups"
-                  :value="affinityGroupIds"
-                  @select-affinity-group-item="($event) => updateAffinityGroups($event)"
-                ></affinity-group-selection>
-              </a-collapse-panel>
-
-              <a-collapse-panel :header="this.$t('networks')" key="networks">
-                <a-collapse
-                  :accordion="false"
-                >
-                  <a-collapse-panel
-                    :header="$t('existingNetworks')"
-                  >
+                      v-if="diskOffering && diskOffering.iscustomized"
+                      input-decorator="size"
+                      @update-disk-size="updateFieldValue" />
+                    <a-form-item class="form-item-hidden">
+                      <a-input v-decorator="['size']"/>
+                    </a-form-item>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="this.$t('Affinity Groups')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template slot="description">
+                  <div v-if="zoneSelected">
+                    <affinity-group-selection
+                      :items="options.affinityGroups"
+                      :value="affinityGroupIds"
+                      :loading="loading.affinityGroups"
+                      @select-affinity-group-item="($event) => updateAffinityGroups($event)"
+                      @handle-search-filter="($event) => handleSearchFilter('affinityGroups', $event)"
+                    ></affinity-group-selection>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="this.$t('networks')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template slot="description">
+                  <div v-if="zoneSelected">
                     <network-selection
                       :items="options.networks"
                       :value="networkOfferingIds"
+                      :loading="loading.networks"
+                      :zoneId="zoneId"
                       @select-network-item="($event) => updateNetworks($event)"
+                      @handle-search-filter="($event) => handleSearchFilter('networks', $event)"
                     ></network-selection>
-                  </a-collapse-panel>
-
-                  <a-collapse-panel
-                    :header="$t('addNewNetworks')"
-                  >
-                    <network-creation></network-creation>
-                  </a-collapse-panel>
-                </a-collapse>
-
-                <network-configuration
-                  v-if="networks.length > 0"
-                  :items="networks"
-                ></network-configuration>
-              </a-collapse-panel>
-
-              <a-collapse-panel :header="this.$t('sshKeyPairs')" key="sshKeyPairs">
-                <ssh-key-pair-selection
-                  :items="options.sshKeyPairs"
-                  :value="sshKeyPair ? sshKeyPair.name : ''"
-                  @select-ssh-key-pair-item="($event) => updateSshKeyPairs($event)"
-                ></ssh-key-pair-selection>
-              </a-collapse-panel>
-            </a-collapse>
-
+                    <network-configuration
+                      v-if="networks.length > 0"
+                      :items="networks"
+                      @update-network-config="($event) => updateNetworkConfig($event)"
+                      @select-default-network-item="($event) => updateDefaultNetworks($event)"
+                    ></network-configuration>
+                  </div>
+                </template>
+              </a-step>
+              <a-step
+                :title="this.$t('sshKeyPairs')"
+                :status="zoneSelected ? 'process' : 'wait'">
+                <template slot="description">
+                  <div v-if="zoneSelected">
+                    <ssh-key-pair-selection
+                      :items="options.sshKeyPairs"
+                      :value="sshKeyPair ? sshKeyPair.name : ''"
+                      :loading="loading.sshKeyPairs"
+                      @select-ssh-key-pair-item="($event) => updateSshKeyPairs($event)"
+                      @handle-search-filter="($event) => handleSearchFilter('sshKeyPairs', $event)"
+                    />
+                  </div>
+                </template>
+              </a-step>
+            </a-steps>
             <div class="card-footer">
               <!-- ToDo extract as component -->
-              <a-button @click="() => this.$router.back()">{{ this.$t('cancel') }}</a-button>
-              <a-button type="primary" @click="handleSubmit">{{ this.$t('submit') }}</a-button>
+              <a-button @click="() => this.$router.back()" :loading="loading.deploy">
+                {{ this.$t('cancel') }}
+              </a-button>
+              <a-button type="primary" @click="handleSubmit" :loading="loading.deploy">
+                <a-icon type="rocket" />
+                {{ this.$t('Launch VM') }}
+              </a-button>
             </div>
           </a-form>
         </a-card>
       </a-col>
       <a-col :md="24" :lg="7" v-if="!isMobile()">
         <a-affix :offsetTop="75">
-          <info-card :resource="vm" :title="this.$t('yourInstance')">
+          <info-card class="vm-info-card" :resource="vm" :title="this.$t('yourInstance')">
             <!-- ToDo: Refactor this, maybe move everything to the info-card component -->
             <div slot="details" v-if="diskSize" style="margin-bottom: 12px;">
               <a-icon type="hdd"></a-icon>
@@ -183,14 +268,12 @@ import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection'
 import AffinityGroupSelection from '@views/compute/wizard/AffinityGroupSelection'
 import NetworkSelection from '@views/compute/wizard/NetworkSelection'
 import NetworkConfiguration from '@views/compute/wizard/NetworkConfiguration'
-import NetworkCreation from '@views/compute/wizard/NetworksCreation'
 import SshKeyPairSelection from '@views/compute/wizard/SshKeyPairSelection'
 
 export default {
   name: 'Wizard',
   components: {
     SshKeyPairSelection,
-    NetworkCreation,
     NetworkConfiguration,
     NetworkSelection,
     AffinityGroupSelection,
@@ -208,6 +291,8 @@ export default {
   mixins: [mixin, mixinDevice],
   data () {
     return {
+      zoneId: '',
+      zoneSelected: false,
       vm: {},
       options: {
         templates: [],
@@ -217,7 +302,27 @@ export default {
         zones: [],
         affinityGroups: [],
         networks: [],
-        sshKeyPairs: []
+        sshKeyPairs: [],
+        pods: [],
+        clusters: [],
+        hosts: [],
+        groups: [],
+        keyboards: []
+      },
+      loading: {
+        deploy: false,
+        templates: false,
+        isos: false,
+        serviceOfferings: false,
+        diskOfferings: false,
+        affinityGroups: false,
+        networks: false,
+        sshKeyPairs: false,
+        zones: false,
+        pods: false,
+        clusters: false,
+        hosts: false,
+        groups: false
       },
       instanceConfig: [],
       template: {},
@@ -226,13 +331,44 @@ export default {
       diskOffering: {},
       affinityGroups: [],
       networks: [],
+      networksAdd: [],
       zone: {},
       sshKeyPair: {},
+      templateFilter: [
+        'featured',
+        'community',
+        'selfexecutable',
+        'sharedexecutable'
+      ],
       isoFilter: [
-        'executable',
+        'featured',
+        'community',
         'selfexecutable',
         'sharedexecutable'
-      ]
+      ],
+      steps: {
+        BASIC: 0,
+        TEMPLATE_ISO: 1,
+        COMPUTE: 2,
+        DISK_OFFERING: 3,
+        AFFINITY_GROUP: 4,
+        NETWORK: 5,
+        SSH_KEY_PAIR: 6
+      },
+      initDataConfig: {},
+      defaultNetwork: '',
+      networkConfig: [],
+      tabList: [
+        {
+          key: 'templateid',
+          tab: this.$t('Templates')
+        },
+        {
+          key: 'isoid',
+          tab: this.$t('ISOs')
+        }
+      ],
+      tabKey: 'templateid'
     }
   },
   computed: {
@@ -255,35 +391,78 @@ export default {
     },
     params () {
       return {
-        templates: {
-          list: 'listTemplates',
+        serviceOfferings: {
+          list: 'listServiceOfferings',
           options: {
-            templatefilter: 'executable',
-            zoneid: _.get(this.zone, 'id')
+            zoneid: _.get(this.zone, 'id'),
+            issystem: false,
+            page: 1,
+            pageSize: 10,
+            keyword: undefined
           }
         },
-        serviceOfferings: {
-          list: 'listServiceOfferings'
-        },
         diskOfferings: {
-          list: 'listDiskOfferings'
+          list: 'listDiskOfferings',
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            page: 1,
+            pageSize: 10,
+            keyword: undefined
+          }
         },
         zones: {
           list: 'listZones'
         },
         affinityGroups: {
-          list: 'listAffinityGroups'
+          list: 'listAffinityGroups',
+          options: {
+            page: 1,
+            pageSize: 10,
+            keyword: undefined
+          }
         },
         sshKeyPairs: {
-          list: 'listSSHKeyPairs'
+          list: 'listSSHKeyPairs',
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            page: 1,
+            pageSize: 10,
+            keyword: undefined
+          }
         },
         networks: {
           list: 'listNetworks',
           options: {
             zoneid: _.get(this.zone, 'id'),
             canusefordeploy: true,
-            projectid: store.getters.project.id
+            projectid: store.getters.project.id,
+            page: 1,
+            pageSize: 10,
+            keyword: undefined
+          }
+        },
+        pods: {
+          list: 'listPods',
+          options: {
+            zoneid: _.get(this.zone, 'id')
           }
+        },
+        clusters: {
+          list: 'listClusters',
+          options: {
+            zoneid: _.get(this.zone, 'id')
+          }
+        },
+        hosts: {
+          list: 'listHosts',
+          options: {
+            zoneid: _.get(this.zone, 'id'),
+            state: 'Up',
+            type: 'Routing'
+          }
+        },
+        groups: {
+          list: 'listInstanceGroups'
         }
       }
     },
@@ -297,9 +476,54 @@ export default {
           value: zone.id
         }
       })
+    },
+    podSelectOptions () {
+      return this.options.pods.map((pod) => {
+        return {
+          label: pod.name,
+          value: pod.id
+        }
+      })
+    },
+    clusterSelectOptions () {
+      return this.options.clusters.map((cluster) => {
+        return {
+          label: cluster.name,
+          value: cluster.id
+        }
+      })
+    },
+    hostSelectOptions () {
+      return this.options.hosts.map((host) => {
+        return {
+          label: host.name,
+          value: host.id
+        }
+      })
+    },
+    keyboardSelectOptions () {
+      return this.options.keyboards.map((keyboard) => {
+        return {
+          label: this.$t(keyboard.description),
+          value: keyboard.id
+        }
+      })
+    },
+    groupsSelectOptions () {
+      return this.options.groups.map((group) => {
+        return {
+          label: group.name,
+          value: group.id
+        }
+      })
     }
   },
   watch: {
+    '$route' (to, from) {
+      if (to.name === 'deployVirtualMachine') {
+        this.resetData()
+      }
+    },
     instanceConfig (instanceConfig) {
       this.template = _.find(this.options.templates, (option) => option.id === instanceConfig.templateid)
       this.iso = _.find(this.options.isos, (option) => option.id === instanceConfig.isoid)
@@ -363,26 +587,99 @@ export default {
         this.vm = this.instanceConfig
       }
     })
-    this.form.getFieldDecorator('computeofferingid', { initialValue: [], preserve: true })
-    this.form.getFieldDecorator('diskofferingid', { initialValue: [], preserve: true })
+    this.form.getFieldDecorator('computeofferingid', { initialValue: undefined, preserve: true })
+    this.form.getFieldDecorator('diskofferingid', { initialValue: undefined, preserve: true })
     this.form.getFieldDecorator('affinitygroupids', { initialValue: [], preserve: true })
-    this.form.getFieldDecorator('isoid', { initialValue: [], preserve: true })
+    this.form.getFieldDecorator('isoid', { initialValue: undefined, preserve: true })
     this.form.getFieldDecorator('networkids', { initialValue: [], preserve: true })
-    this.form.getFieldDecorator('keypair', { initialValue: [], preserve: true })
+    this.form.getFieldDecorator('keypair', { initialValue: undefined, preserve: true })
+    this.apiParams = {}
+    this.apiDeployVirtualMachine = this.$store.getters.apis.deployVirtualMachine || {}
+    this.apiDeployVirtualMachine.params.forEach(param => {
+      this.apiParams[param.name] = param
+    })
   },
   created () {
-    _.each(this.params, this.fetchOptions)
-    Vue.nextTick().then(() => {
-      this.instanceConfig = this.form.getFieldsValue() // ToDo: maybe initialize with some other defaults
-    })
+    this.fetchData()
   },
   methods: {
+    fetchData () {
+      this.fetchOptions(this.params.zones, 'zones')
+      this.fetchOptions(this.params.pods, 'pods')
+      this.fetchOptions(this.params.clusters, 'clusters')
+      this.fetchOptions(this.params.hosts, 'hosts')
+      this.fetchOptions(this.params.groups, 'groups')
+      this.fetchKeyboard()
+      Vue.nextTick().then(() => {
+        this.instanceConfig = this.form.getFieldsValue() // ToDo: maybe initialize with some other defaults
+      })
+    },
+    fetchKeyboard () {
+      const keyboardType = []
+      keyboardType.push({
+        id: '',
+        description: ''
+      })
+      keyboardType.push({
+        id: 'us',
+        description: 'label.standard.us.keyboard'
+      })
+      keyboardType.push({
+        id: 'uk',
+        description: 'label.uk.keyboard'
+      })
+      keyboardType.push({
+        id: 'fr',
+        description: 'label.french.azerty.keyboard'
+      })
+      keyboardType.push({
+        id: 'jp',
+        description: 'label.japanese.keyboard'
+      })
+      keyboardType.push({
+        id: 'sc',
+        description: 'label.simplified.chinese.keyboard'
+      })
+
+      this.$set(this.options, 'keyboards', keyboardType)
+    },
+    resetData () {
+      this.vm = {}
+      this.zoneSelected = false
+      this.form.resetFields()
+      this.fetchData()
+    },
+    updateFieldValue (name, value) {
+      if (name === 'templateid') {
+        this.tabKey = 'templateid'
+        this.form.setFieldsValue({
+          templateid: value,
+          isoid: undefined
+        })
+      } else if (name === 'isoid') {
+        this.tabKey = 'isoid'
+        this.form.setFieldsValue({
+          isoid: value,
+          templateid: undefined
+        })
+      } else {
+        this.form.setFieldsValue({
+          [name]: value
+        })
+      }
+    },
     updateComputeOffering (id) {
       this.form.setFieldsValue({
         computeofferingid: id
       })
     },
     updateDiskOffering (id) {
+      if (id === '0') {
+        this.form.setFieldsValue({
+          diskofferingid: undefined
+        })
+        return
+      }
       this.form.setFieldsValue({
         diskofferingid: id
       })
@@ -397,7 +694,19 @@ export default {
         networkids: ids
       })
     },
+    updateDefaultNetworks (id) {
+      this.defaultNetwork = id
+    },
+    updateNetworkConfig (networks) {
+      this.networkConfig = networks
+    },
     updateSshKeyPairs (name) {
+      if (name === this.$t('noselect')) {
+        this.form.setFieldsValue({
+          keypair: undefined
+        })
+        return
+      }
       this.form.setFieldsValue({
         keypair: name
       })
@@ -405,10 +714,103 @@ export default {
     getText (option) {
       return _.get(option, 'displaytext', _.get(option, 'name'))
     },
-    handleSubmit () {
+    handleSubmit (e) {
       console.log('wizard submit')
+      e.preventDefault()
+      this.form.validateFields((err, values) => {
+        if (err) {
+          return
+        }
+        const deployVmData = {}
+        // step 1 : select zone
+        deployVmData.zoneid = values.zoneid
+        deployVmData.podid = values.podid
+        deployVmData.clusterid = values.clusterid
+        deployVmData.hostid = values.hostid
+        deployVmData.group = values.group
+        deployVmData.keyboard = values.keyboard
+        if (values.keyboard && values.keyboard.length > 0) {
+          deployVmData.userdata = encodeURIComponent(btoa(this.sanitizeReverse(values.keyboard)))
+        }
+        // step 2: select template/iso
+        if (this.tabKey === 'templateid') {
+          deployVmData.templateid = values.templateid
+        } else {
+          deployVmData.templateid = values.isoid
+        }
+        if (values.rootdisksize && values.rootdisksize > 0) {
+          deployVmData.rootdisksize = values.rootdisksize
+        }
+        // step 3: select service offering
+        deployVmData.serviceofferingid = values.computeofferingid
+        // step 4: select disk offering
+        deployVmData.diskofferingid = values.diskofferingid
+        if (values.size) {
+          deployVmData.size = values.size
+        }
+        // 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
+              }
+            }
+          }
+        }
+        // step 7: select ssh key pair
+        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
+        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
+                }
+                this.$store.dispatch('AddAsyncJob', {
+                  title: title,
+                  jobid: jobId,
+                  description: successDescription,
+                  status: 'progress'
+                })
+              },
+              loadingMessage: `${title} in progress for ${description}`,
+              catchMessage: 'Error encountered while fetching async job result'
+            })
+          }
+          this.$router.back()
+        }).catch(error => {
+          this.$notification.error({
+            message: 'Request Failed',
+            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
+          })
+        }).finally(() => {
+          this.loading.deploy = false
+        })
+      })
     },
-    fetchOptions (param, name) {
+    fetchOptions (param, name, exclude) {
+      if (exclude && exclude.length > 0) {
+        if (exclude.includes(name)) {
+          return
+        }
+      }
+      this.loading[name] = true
       param.loading = true
       param.opts = []
       const options = param.options || {}
@@ -416,6 +818,11 @@ export default {
       api(param.list, options).then((response) => {
         param.loading = false
         _.map(response, (responseItem, responseKey) => {
+          if (Object.keys(responseItem).length === 0) {
+            this.options[name] = []
+            this.$forceUpdate()
+            return
+          }
           if (!responseKey.includes('response')) {
             return
           }
@@ -431,41 +838,108 @@ export default {
       }).catch(function (error) {
         console.log(error.stack)
         param.loading = false
+      }).finally(() => {
+        this.loading[name] = false
+      })
+    },
+    fetchTemplates (templateFilter) {
+      return new Promise((resolve, reject) => {
+        api('listTemplates', {
+          zoneid: _.get(this.zone, 'id'),
+          templatefilter: templateFilter
+        }).then((response) => {
+          resolve(response)
+        }).catch((reason) => {
+          // ToDo: Handle errors
+          reject(reason)
+        })
       })
     },
     fetchIsos (isoFilter) {
-      api('listIsos', {
-        zoneid: _.get(this.zone, 'id'),
-        isofilter: isoFilter,
-        bootable: true
-      }).then((response) => {
-        const concatedIsos = _.concat(this.options.isos, _.get(response, 'listisosresponse.iso', []))
-        this.options.isos = _.uniqWith(concatedIsos, _.isEqual)
-        this.$forceUpdate()
+      return new Promise((resolve, reject) => {
+        api('listIsos', {
+          zoneid: _.get(this.zone, 'id'),
+          isofilter: isoFilter,
+          bootable: true
+        }).then((response) => {
+          resolve(response)
+        }).catch((reason) => {
+          // ToDo: Handle errors
+          reject(reason)
+        })
+      })
+    },
+    fetchAllTemplates () {
+      const promises = []
+      this.options.templates = []
+      this.loading.templates = true
+      this.templateFilter.forEach((filter) => {
+        promises.push(this.fetchTemplates(filter))
+      })
+      Promise.all(promises).then(response => {
+        response.forEach((resItem) => {
+          const concatTemplates = _.concat(this.options.templates, _.get(resItem, 'listtemplatesresponse.template', []))
+          this.options.templates = _.uniqWith(concatTemplates, _.isEqual)
+          this.$forceUpdate()
+        })
       }).catch((reason) => {
-        // ToDo: Handle errors
         console.log(reason)
+      }).finally(() => {
+        this.loading.templates = false
       })
     },
     fetchAllIsos () {
+      const promises = []
       this.options.isos = []
+      this.loading.isos = true
       this.isoFilter.forEach((filter) => {
-        this.fetchIsos(filter)
+        promises.push(this.fetchIsos(filter))
+      })
+      Promise.all(promises).then(response => {
+        response.forEach((resItem) => {
+          const concatedIsos = _.concat(this.options.isos, _.get(resItem, 'listisosresponse.iso', []))
+          this.options.isos = _.uniqWith(concatedIsos, _.isEqual)
+          this.$forceUpdate()
+        })
+      }).catch((reason) => {
+        console.log(reason)
+      }).finally(() => {
+        this.loading.isos = false
       })
     },
-    onTemplatesIsosCollapseChange (key) {
-      if (key === 'isos' && this.options.isos.length === 0) {
+    onSelectZoneId (value) {
+      this.zoneId = value
+      this.zoneSelected = true
+      this.form.setFieldsValue({
+        clusterid: undefined,
+        podid: undefined,
+        hostid: undefined,
+        templateid: undefined,
+        isoid: undefined
+      })
+      this.tabKey = 'templateid'
+      _.each(this.params, (param, name) => {
+        this.fetchOptions(param, name, ['zones', 'groups'])
+      })
+      this.fetchAllTemplates()
+    },
+    handleSearchFilter (name, options) {
+      this.params[name].options = { ...this.params[name].options, ...options }
+      this.fetchOptions(this.params[name], name)
+    },
+    onTabChange (key, type) {
+      this[type] = key
+      if (key === 'isoid') {
         this.fetchAllIsos()
       }
     },
-    onSelectZoneId () {
-      this.$nextTick(() => {
-        if (this.options.isos.length !== 0) {
-          this.fetchAllIsos()
-        }
-        this.fetchOptions(this.params.templates, 'templates')
-        this.fetchOptions(this.params.networks, 'networks')
-      })
+    sanitizeReverse (value) {
+      const reversedValue = value
+        .replace(/&amp;/g, '&')
+        .replace(/&lt;/g, '<')
+        .replace(/&gt;/g, '>')
+
+      return reversedValue
     }
   }
 }
@@ -505,4 +979,22 @@ export default {
     border-radius: @border-radius-base !important;
     margin: 0 0 1.2rem;
   }
+
+  .vm-info-card {
+    .resource-detail-item__label {
+      font-weight: normal;
+    }
+
+    .resource-detail-item__details, .resource-detail-item {
+      a {
+        color: rgba(0, 0, 0, 0.65);
+        cursor: default;
+        pointer-events: none;
+      }
+    }
+  }
+
+  .form-item-hidden {
+    display: none;
+  }
 </style>
diff --git a/src/views/compute/KubernetesServiceTab.vue b/src/views/compute/KubernetesServiceTab.vue
index d71d5a6..daa7851 100644
--- a/src/views/compute/KubernetesServiceTab.vue
+++ b/src/views/compute/KubernetesServiceTab.vue
@@ -19,7 +19,7 @@
   <a-spin :spinning="networkLoading">
     <a-tabs
       :activeKey="currentTab"
-      :tabPosition="device === 'tablet' || device === 'mobile' ? 'top' : 'left'"
+      :tabPosition="device === 'mobile' ? 'top' : 'left'"
       :animated="false"
       @change="handleChangeTab">
       <a-tab-pane :tab="$t('details')" key="details">
diff --git a/src/views/compute/wizard/AffinityGroupSelection.vue b/src/views/compute/wizard/AffinityGroupSelection.vue
index c13cbcc..be14d6f 100644
--- a/src/views/compute/wizard/AffinityGroupSelection.vue
+++ b/src/views/compute/wizard/AffinityGroupSelection.vue
@@ -16,15 +16,24 @@
 // under the License.
 
 <template>
-  <a-table
-    :columns="columns"
-    :dataSource="items"
-    :rowKey="record => record.id"
-    :pagination="{showSizeChanger: true}"
-    :rowSelection="rowSelection"
-    size="middle"
-  >
-  </a-table>
+  <div>
+    <a-input-search
+      style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+      placeholder="Search"
+      v-model="filter"
+      @search="handleSearch" />
+    <a-table
+      :loading="loading"
+      :columns="columns"
+      :dataSource="items"
+      :rowKey="record => record.id"
+      :pagination="{showSizeChanger: true}"
+      :rowSelection="rowSelection"
+      size="middle"
+      :scroll="{ y: 225 }"
+    >
+    </a-table>
+  </div>
 </template>
 
 <script>
@@ -40,10 +49,15 @@ export default {
     value: {
       type: Array,
       default: () => []
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   data () {
     return {
+      filter: '',
       columns: [
         {
           dataIndex: 'name',
@@ -60,6 +74,13 @@ export default {
     }
   },
   computed: {
+    options () {
+      return {
+        page: 1,
+        pageSize: 10,
+        keyword: ''
+      }
+    },
     rowSelection () {
       return {
         type: 'checkbox',
@@ -76,6 +97,18 @@ export default {
         this.selectedRowKeys = newValue
       }
     }
+  },
+  methods: {
+    handleSearch (value) {
+      this.filter = value
+      this.options.keyword = this.filter
+      this.$emit('handle-search-filter', this.options)
+    },
+    handleTableChange (pagination) {
+      this.options.page = pagination.current
+      this.options.pageSize = pagination.pageSize
+      this.$emit('handle-search-filter', this.options)
+    }
   }
 }
 </script>
diff --git a/src/views/compute/wizard/ComputeSelection.vue b/src/views/compute/wizard/ComputeSelection.vue
index dfe4e49..e44d17a 100644
--- a/src/views/compute/wizard/ComputeSelection.vue
+++ b/src/views/compute/wizard/ComputeSelection.vue
@@ -16,16 +16,26 @@
 // under the License.
 
 <template>
-  <a-table
-    :columns="columns"
-    :dataSource="tableSource"
-    :pagination="{showSizeChanger: true}"
-    :rowSelection="rowSelection"
-    size="middle"
-  >
-    <span slot="cpuTitle"><a-icon type="appstore" /> {{ $t('cpu') }}</span>
-    <span slot="ramTitle"><a-icon type="bulb" /> {{ $t('memory') }}</span>
-  </a-table>
+  <div>
+    <a-input-search
+      style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+      placeholder="Search"
+      v-model="filter"
+      @search="handleSearch" />
+    <a-table
+      :columns="columns"
+      :dataSource="tableSource"
+      :pagination="{showSizeChanger: true}"
+      :rowSelection="rowSelection"
+      :loading="loading"
+      size="middle"
+      @change="handleTableChange"
+      :scroll="{ y: 225 }"
+    >
+      <span slot="cpuTitle"><a-icon type="appstore" /> {{ $t('cpu') }}</span>
+      <span slot="ramTitle"><a-icon type="bulb" /> {{ $t('memory') }}</span>
+    </a-table>
+  </div>
 </template>
 
 <script>
@@ -39,10 +49,15 @@ export default {
     value: {
       type: String,
       default: ''
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   data () {
     return {
+      filter: '',
       columns: [
         {
           dataIndex: 'name',
@@ -64,6 +79,13 @@ export default {
     }
   },
   computed: {
+    options () {
+      return {
+        page: 1,
+        pageSize: 10,
+        keyword: ''
+      }
+    },
     tableSource () {
       return this.computeItems.map((item) => {
         return {
@@ -78,9 +100,7 @@ export default {
       return {
         type: 'radio',
         selectedRowKeys: this.selectedRowKeys,
-        onSelect: (row) => {
-          this.$emit('select-compute-item', row.key)
-        }
+        onChange: this.onSelectRow
       }
     }
   },
@@ -90,6 +110,22 @@ export default {
         this.selectedRowKeys = [newValue]
       }
     }
+  },
+  methods: {
+    onSelectRow (value) {
+      this.selectedRowKeys = value
+      this.$emit('select-compute-item', value[0])
+    },
+    handleSearch (value) {
+      this.filter = value
+      this.options.keyword = this.filter
+      this.$emit('handle-search-filter', this.options)
+    },
+    handleTableChange (pagination) {
+      this.options.page = pagination.current
+      this.options.pageSize = pagination.pageSize
+      this.$emit('handle-search-filter', this.options)
+    }
   }
 }
 </script>
diff --git a/src/views/compute/wizard/DiskOfferingSelection.vue b/src/views/compute/wizard/DiskOfferingSelection.vue
index f563745..33d94ac 100644
--- a/src/views/compute/wizard/DiskOfferingSelection.vue
+++ b/src/views/compute/wizard/DiskOfferingSelection.vue
@@ -16,20 +16,37 @@
 // under the License.
 
 <template>
-  <a-table
-    :columns="columns"
-    :dataSource="tableSource"
-    :pagination="{showSizeChanger: true}"
-    :rowSelection="rowSelection"
-    size="middle"
-  >
-    <span slot="diskSizeTitle"><a-icon type="hdd" /> {{ $t('disksize') }}</span>
-    <span slot="iopsTitle"><a-icon type="rocket" /> {{ $t('minMaxIops') }}</span>
-    <template slot="diskSize" slot-scope="text, record">
-      <div v-if="record.isCustomized">{{ $t('isCustomized') }}</div>
-      <div v-else>{{ record.diskSize }} GB</div>
-    </template>
-  </a-table>
+  <div>
+    <a-input-search
+      style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+      placeholder="Search"
+      v-model="filter"
+      @search="handleSearch" />
+    <a-table
+      :loading="loading"
+      :columns="columns"
+      :dataSource="tableSource"
+      :pagination="{showSizeChanger: true}"
+      :rowSelection="rowSelection"
+      size="middle"
+      @change="handleTableChange"
+      :scroll="{ y: 225 }"
+    >
+      <span slot="diskSizeTitle"><a-icon type="hdd" /> {{ $t('disksize') }}</span>
+      <span slot="iopsTitle"><a-icon type="rocket" /> {{ $t('minMaxIops') }}</span>
+      <template slot="diskSize" slot-scope="text, record">
+        <div v-if="record.isCustomized">{{ $t('isCustomized') }}</div>
+        <div v-else-if="record.diskSize">{{ record.diskSize }} GB</div>
+        <div v-else>-</div>
+      </template>
+      <template slot="iops" slot-scope="text, record">
+        <span v-if="record.miniops && record.maxiops">{{ record.miniops }} - {{ record.maxiops }}</span>
+        <span v-else-if="record.miniops && !record.maxiops">{{ record.miniops }}</span>
+        <span v-else-if="!record.miniops && record.maxiops">{{ record.maxiops }}</span>
+        <span v-else>-</span>
+      </template>
+    </a-table>
+  </div>
 </template>
 
 <script>
@@ -43,18 +60,23 @@ export default {
     value: {
       type: String,
       default: ''
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   data () {
     return {
+      filter: '',
       columns: [
         {
           dataIndex: 'name',
-          title: this.$t('diskOffering'),
+          title: this.$t('diskoffering'),
           width: '40%'
         },
         {
-          dataIndex: 'diskSize',
+          dataIndex: 'disksize',
           slots: { title: 'diskSizeTitle' },
           width: '30%',
           scopedSlots: { customRender: 'diskSize' }
@@ -62,20 +84,41 @@ export default {
         {
           dataIndex: 'iops',
           slots: { title: 'iopsTitle' },
-          width: '30%'
+          width: '30%',
+          scopedSlots: { customRender: 'iops' }
         }
       ],
-      selectedRowKeys: []
+      selectedRowKeys: ['0'],
+      dataItems: []
     }
   },
+  created () {
+    this.dataItems = []
+    this.dataItems.push({
+      id: '0',
+      name: this.$t('noselect'),
+      diskSize: undefined,
+      miniops: undefined,
+      maxiops: undefined,
+      isCustomized: undefined
+    })
+  },
   computed: {
+    options () {
+      return {
+        page: 1,
+        pageSize: 10,
+        keyword: ''
+      }
+    },
     tableSource () {
-      return this.items.map((item) => {
+      return this.dataItems.map((item) => {
         return {
           key: item.id,
           name: item.name,
           diskSize: item.disksize,
-          iops: `${item.miniops} – ${item.maxiops}`,
+          miniops: item.miniops,
+          maxiops: item.maxiops,
           isCustomized: item.iscustomized
         }
       })
@@ -84,9 +127,7 @@ export default {
       return {
         type: 'radio',
         selectedRowKeys: this.selectedRowKeys,
-        onSelect: (row) => {
-          this.$emit('select-disk-offering-item', row.key)
-        }
+        onChange: this.onSelectRow
       }
     }
   },
@@ -95,6 +136,27 @@ export default {
       if (newValue && newValue !== oldValue) {
         this.selectedRowKeys = [newValue]
       }
+    },
+    items (newData, oldData) {
+      if (newData && newData.length > 0) {
+        this.dataItems = this.dataItems.concat(newData)
+      }
+    }
+  },
+  methods: {
+    onSelectRow (value) {
+      this.selectedRowKeys = value
+      this.$emit('select-disk-offering-item', value[0])
+    },
+    handleSearch (value) {
+      this.filter = value
+      this.options.keyword = this.filter
+      this.$emit('handle-search-filter', this.options)
+    },
+    handleTableChange (pagination) {
+      this.options.page = pagination.current
+      this.options.pageSize = pagination.pageSize
+      this.$emit('handle-search-filter', this.options)
     }
   }
 }
diff --git a/src/views/compute/wizard/DiskSizeSelection.vue b/src/views/compute/wizard/DiskSizeSelection.vue
index f973741..bec025a 100644
--- a/src/views/compute/wizard/DiskSizeSelection.vue
+++ b/src/views/compute/wizard/DiskSizeSelection.vue
@@ -22,14 +22,13 @@
         <a-slider
           :min="0"
           :max="1024"
-          v-decorator="[inputDecorator]"
+          v-model="inputValue"
+          @change="($event) => updateDickSize($event)"
         />
       </a-col>
       <a-col :span="4">
         <a-input-number
-          v-decorator="[inputDecorator, {
-            rules: [{ required: false, message: 'Please enter a number' }]
-          }]"
+          v-model="inputValue"
           :formatter="value => `${value} GB`"
           :parser="value => value.replace(' GB', '')"
         />
@@ -46,6 +45,16 @@ export default {
       type: String,
       default: ''
     }
+  },
+  data () {
+    return {
+      inputValue: 0
+    }
+  },
+  methods: {
+    updateDickSize (value) {
+      this.$emit('update-disk-size', this.inputDecorator, value)
+    }
   }
 }
 </script>
diff --git a/src/views/compute/wizard/NetworkConfiguration.vue b/src/views/compute/wizard/NetworkConfiguration.vue
index d647e9d..c53ed19 100644
--- a/src/views/compute/wizard/NetworkConfiguration.vue
+++ b/src/views/compute/wizard/NetworkConfiguration.vue
@@ -18,21 +18,24 @@
 <template>
   <a-table
     :columns="columns"
-    :dataSource="items"
+    :dataSource="dataItems"
     :pagination="{showSizeChanger: true}"
     :rowSelection="rowSelection"
     :rowKey="record => record.id"
     size="middle"
+    :scroll="{ y: 225 }"
   >
-    <template v-slot:ipAddress="text">
+    <template slot="ipAddress" slot-scope="text, record">
       <a-input
-        :value="text"
-      ></a-input>
+        style="width: 150px;"
+        :placeholder="$t('ipAddress')"
+        @change="($event) => updateNetworkData('ipAddress', record.id, $event.target.value)" />
     </template>
-    <template v-slot:macAddress="text">
+    <template slot="macAddress" slot-scope="text, record">
       <a-input
-        :value="text"
-      ></a-input>
+        style="width: 150px;"
+        :placeholder="$t('macAddress')"
+        @change="($event) => updateNetworkData('macAddress', record.id, $event.target.value)" />
     </template>
   </a-table>
 </template>
@@ -52,6 +55,7 @@ export default {
   },
   data () {
     return {
+      networks: [],
       columns: [
         {
           dataIndex: 'name',
@@ -71,17 +75,24 @@ export default {
           scopedSlots: { customRender: 'macAddress' }
         }
       ],
-      selectedRowKeys: []
+      selectedRowKeys: [],
+      dataItems: []
     }
   },
+  beforeCreate () {
+    this.dataItems = []
+  },
+  created () {
+    this.dataItems = this.items
+    this.selectedRowKeys = [this.dataItems[0].id]
+    this.$emit('select-default-network-item', this.selectedRowKeys)
+  },
   computed: {
     rowSelection () {
       return {
         type: 'radio',
         selectedRowKeys: this.selectedRowKeys,
-        onSelect: (row) => {
-          this.$emit('select-default-network-item', row.key)
-        }
+        onChange: this.onSelectRow
       }
     }
   },
@@ -90,6 +101,38 @@ export default {
       if (newValue && newValue !== oldValue) {
         this.selectedRowKeys = [newValue]
       }
+    },
+    items (newData, oldData) {
+      if (newData && newData.length > 0) {
+        this.dataItems = newData
+        const keyEx = this.dataItems.filter((item) => this.selectedRowKeys.includes(item.id))
+        if (!keyEx || keyEx.length === 0) {
+          this.selectedRowKeys = [this.dataItems[0].id]
+        }
+      }
+    }
+  },
+  methods: {
+    onSelectRow (value) {
+      this.selectedRowKeys = value
+      this.$emit('select-default-network-item', value[0])
+    },
+    updateNetworkData (name, key, value) {
+      if (this.networks.length === 0) {
+        const networkItem = {}
+        networkItem.key = key
+        networkItem[name] = value
+        this.networks.push(networkItem)
+        this.$emit('update-network-config', this.networks)
+        return
+      }
+
+      this.networks.filter((item, index) => {
+        if (item.key === key) {
+          this.$set(this.networks[index], name, value)
+        }
+      })
+      this.$emit('update-network-config', this.networks)
     }
   }
 }
diff --git a/src/views/compute/wizard/NetworkSelection.vue b/src/views/compute/wizard/NetworkSelection.vue
index 5eeeb34..21151f2 100644
--- a/src/views/compute/wizard/NetworkSelection.vue
+++ b/src/views/compute/wizard/NetworkSelection.vue
@@ -16,29 +16,39 @@
 // under the License.
 
 <template>
-  <a-table
-    :columns="columns"
-    :dataSource="networkItems"
-    :rowKey="record => record.id"
-    :pagination="{showSizeChanger: true}"
-    :rowSelection="rowSelection"
-  >
-    <a-list
-      slot="expandedRowRender"
-      slot-scope="record"
-      :key="record.id"
-      :dataSource="getDetails(record)"
-      size="small"
+  <div>
+    <a-input-search
+      style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+      placeholder="Search"
+      v-model="filter"
+      @search="handleSearch" />
+    <a-table
+      :loading="loading"
+      :columns="columns"
+      :dataSource="networkItems"
+      :rowKey="record => record.id"
+      :pagination="{showSizeChanger: true, size: 'small'}"
+      :rowSelection="rowSelection"
+      @change="handleTableChange"
+      :scroll="{ y: 225 }"
     >
-      <a-list-item slot="renderItem" slot-scope="item" :key="item.id">
-        <a-list-item-meta
-          :description="item.description"
-        >
-          <template v-slot:title>{{ item.title }}</template>
-        </a-list-item-meta>
-      </a-list-item>
-    </a-list>
-  </a-table>
+      <a-list
+        slot="expandedRowRender"
+        slot-scope="record"
+        :key="record.id"
+        :dataSource="getDetails(record)"
+        size="small"
+      >
+        <a-list-item slot="renderItem" slot-scope="item" :key="item.id">
+          <a-list-item-meta
+            :description="item.description"
+          >
+            <template v-slot:title>{{ item.title }}</template>
+          </a-list-item-meta>
+        </a-list-item>
+      </a-list>
+    </a-table>
+  </div>
 </template>
 
 <script>
@@ -56,16 +66,28 @@ export default {
     value: {
       type: Array,
       default: () => []
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   data () {
     return {
+      filter: '',
       selectedRowKeys: [],
       vpcs: [],
       filteredInfo: null
     }
   },
   computed: {
+    options () {
+      return {
+        page: 1,
+        pageSize: 10,
+        keyword: ''
+      }
+    },
     columns () {
       let vpcFilter = []
       if (this.vpcs) {
@@ -146,6 +168,16 @@ export default {
           description: network.networkofferingdisplaytext
         }
       ]
+    },
+    handleSearch (value) {
+      this.filter = value
+      this.options.keyword = this.filter
+      this.$emit('handle-search-filter', this.options)
+    },
+    handleTableChange (pagination) {
+      this.options.page = pagination.current
+      this.options.pageSize = pagination.pageSize
+      this.$emit('handle-search-filter', this.options)
     }
   }
 }
diff --git a/src/views/compute/wizard/NetworksCreation.vue b/src/views/compute/wizard/NetworksCreation.vue
deleted file mode 100644
index a708402..0000000
--- a/src/views/compute/wizard/NetworksCreation.vue
+++ /dev/null
@@ -1,150 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-<template>
-  <div>
-    <a-table
-      v-if="networkItems.length > 0"
-      :columns="columns"
-      :dataSource="networkItems"
-      :pagination="false"
-    >
-      <template v-slot:name="text">
-        <a-input
-          :value="text"
-        ></a-input>
-      </template>
-      <template v-slot:operation>
-        <a-popconfirm
-          v-if="networkItems.length"
-          title="Sure to delete?"
-          @confirm="removeItem()"
-        >
-          <a-button type="link">Delete</a-button>
-        </a-popconfirm>
-      </template>
-      <template v-slot:networkOffering>
-        <a-select
-          :placeholder="$t('networkOfferingId')"
-          :options="networkOfferingOptions"
-        ></a-select>
-      </template>
-      <template v-slot:vpc>
-        <a-select
-          :placeholder="$t('vpc')"
-          :options="vpcOptions"
-        ></a-select>
-      </template>
-    </a-table>
-
-    <div style="text-align: right; margin-top: 1rem;">
-      <a-button
-        type="primary"
-        @click="addNewItem"
-      >{{ $t('addAnotherNetwork') }}
-      </a-button>
-    </div>
-  </div>
-</template>
-
-<script>
-import { api } from '@/api'
-import store from '@/store'
-import _ from 'lodash'
-
-/*
- * ToDo: Implement real functionality
- */
-export default {
-  name: 'NetworkCreation',
-  data () {
-    return {
-      networkItems: [{}],
-      columns: [
-        {
-          dataIndex: 'name',
-          title: this.$t('networks'),
-          scopedSlots: { customRender: 'name' },
-          width: '30%'
-        },
-        {
-          dataIndex: 'offering',
-          title: this.$t('networkOfferingId'),
-          scopedSlots: { customRender: 'networkOffering' },
-          width: '30%'
-        },
-        {
-          dataIndex: 'vpcName',
-          title: this.$t('VPC'),
-          scopedSlots: { customRender: 'vpc' },
-          width: '30%'
-        },
-        {
-          dataIndex: 'action',
-          scopedSlots: { customRender: 'operation' },
-          width: '10%'
-        }
-      ],
-      networkOfferings: [],
-      vpcs: []
-    }
-  },
-  computed: {
-    networkOfferingOptions () {
-      return this.networkOfferings.map((offering) => {
-        return {
-          label: offering.name,
-          value: offering.id
-        }
-      })
-    },
-    vpcOptions () {
-      return this.vpcs.map((vpc) => {
-        return {
-          label: vpc.name,
-          value: vpc.id
-        }
-      })
-    }
-  },
-  created () {
-    api('listNetworkOfferings', {
-      // ToDo: Add the zoneId
-    }).then((response) => {
-      this.networkOfferings = _.get(response, 'listnetworkofferingsresponse.networkoffering')
-    })
-    // ToDo: Remove this redundant api call – see the NetworkSelection component
-    api('listVPCs', {
-      projectid: store.getters.project.id
-    }).then((response) => {
-      this.vpcs = _.get(response, 'listvpcsresponse.vpc')
-    })
-  },
-  methods: {
-    addNewItem () {
-      this.networkItems.push({})
-    },
-    removeItem () {
-      this.networkItems.pop()
-    }
-  }
-}
-</script>
-
-<style scoped>
-
-</style>
diff --git a/src/views/compute/wizard/SshKeyPairSelection.vue b/src/views/compute/wizard/SshKeyPairSelection.vue
index 5e62a24..420f406 100644
--- a/src/views/compute/wizard/SshKeyPairSelection.vue
+++ b/src/views/compute/wizard/SshKeyPairSelection.vue
@@ -16,16 +16,26 @@
 // under the License.
 
 <template>
-  <a-table
-    :columns="columns"
-    :dataSource="tableSource"
-    :pagination="{showSizeChanger: true}"
-    :rowSelection="rowSelection"
-    size="middle"
-  >
-    <template v-slot:account><a-icon type="user" /> {{ $t('account') }}</template>
-    <template v-slot:domain><a-icon type="block" /> {{ $t('domain') }}</template>
-  </a-table>
+  <div>
+    <a-input-search
+      style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+      placeholder="Search"
+      v-model="filter"
+      @search="handleSearch" />
+    <a-table
+      :loading="loading"
+      :columns="columns"
+      :dataSource="tableSource"
+      :pagination="{showSizeChanger: true}"
+      :rowSelection="rowSelection"
+      size="middle"
+      @change="handleTableChange"
+      :scroll="{ y: 225 }"
+    >
+      <template v-slot:account><a-icon type="user" /> {{ $t('account') }}</template>
+      <template v-slot:domain><a-icon type="block" /> {{ $t('domain') }}</template>
+    </a-table>
+  </div>
 </template>
 
 <script>
@@ -39,10 +49,15 @@ export default {
     value: {
       type: String,
       default: ''
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   data () {
     return {
+      filter: '',
       columns: [
         {
           dataIndex: 'name',
@@ -60,12 +75,28 @@ export default {
           width: '30%'
         }
       ],
-      selectedRowKeys: []
+      selectedRowKeys: [this.$t('noselect')],
+      dataItems: []
     }
   },
+  created () {
+    this.dataItems = []
+    this.dataItems.push({
+      name: this.$t('noselect'),
+      account: '-',
+      domain: '-'
+    })
+  },
   computed: {
+    options () {
+      return {
+        page: 1,
+        pageSize: 10,
+        keyword: ''
+      }
+    },
     tableSource () {
-      return this.items.map((item) => {
+      return this.dataItems.map((item) => {
         return {
           key: item.name,
           name: item.name,
@@ -78,9 +109,7 @@ export default {
       return {
         type: 'radio',
         selectedRowKeys: this.selectedRowKeys,
-        onSelect: (row) => {
-          this.$emit('select-ssh-key-pair-item', row.key)
-        }
+        onChange: this.onSelectRow
       }
     }
   },
@@ -89,6 +118,27 @@ export default {
       if (newValue && newValue !== oldValue) {
         this.selectedRowKeys = [newValue]
       }
+    },
+    items (newData, oldData) {
+      if (newData && newData.length > 0) {
+        this.dataItems = this.dataItems.concat(newData)
+      }
+    }
+  },
+  methods: {
+    onSelectRow (value) {
+      this.selectedRowKeys = value
+      this.$emit('select-ssh-key-pair-item', value[0])
+    },
+    handleSearch (value) {
+      this.filter = value
+      this.options.keyword = this.filter
+      this.$emit('handle-search-filter', this.options)
+    },
+    handleTableChange (pagination) {
+      this.options.page = pagination.current
+      this.options.pageSize = pagination.pageSize
+      this.$emit('handle-search-filter', this.options)
     }
   }
 }
diff --git a/src/views/compute/wizard/TemplateIsoRadioGroup.vue b/src/views/compute/wizard/TemplateIsoRadioGroup.vue
index cb879e1..594b2d0 100644
--- a/src/views/compute/wizard/TemplateIsoRadioGroup.vue
+++ b/src/views/compute/wizard/TemplateIsoRadioGroup.vue
@@ -17,37 +17,46 @@
 
 <template>
   <a-form-item>
-    <a-radio-group
-      v-for="(os, osIndex) in osList"
-      :key="osIndex"
-      class="radio-group"
-      v-decorator="[inputDecorator, {
-        rules: [{ required: true, message: 'Please select option' }]
-      }]"
-    >
-      <a-radio
-        class="radio-group__radio"
-        :value="os.id"
-      >
-        {{ os.displaytext }}&nbsp;
-        <a-tag
-          :visible="os.ispublic && !os.isfeatured"
-          color="blue"
-        >{{ $t('isPublic') }}</a-tag>
-        <a-tag
-          :visible="os.isfeatured"
-          color="green"
-        >{{ $t('isFeatured') }}</a-tag>
-        <a-tag
-          :visible="isSelf(os)"
-          color="orange"
-        >{{ $t('isSelf') }}</a-tag>
-        <a-tag
-          :visible="isShared(os)"
-          color="cyan"
-        >{{ $t('isShared') }}</a-tag>
-      </a-radio>
-    </a-radio-group>
+    <a-list
+      class="form-item-scroll"
+      itemLayout="vertical"
+      size="small"
+      :dataSource="osList"
+      :pagination="pagination">
+      <a-list-item slot="renderItem" slot-scope="os, osIndex" key="os.id">
+        <a-radio-group
+          class="radio-group"
+          :key="osIndex"
+          v-model="value"
+          @change="($event) => updateSelectionTemplateIso($event.target.value)">
+          <a-radio
+            class="radio-group__radio"
+            :value="os.id">
+            {{ os.displaytext }}&nbsp;
+            <a-tag
+              :visible="os.ispublic && !os.isfeatured"
+              color="blue"
+              @click="onFilterTag('is: public')"
+            >{{ $t('isPublic') }}</a-tag>
+            <a-tag
+              :visible="os.isfeatured"
+              color="green"
+              @click="onFilterTag('is: featured')"
+            >{{ $t('isFeatured') }}</a-tag>
+            <a-tag
+              :visible="isSelf(os)"
+              color="orange"
+              @click="onFilterTag('is: self')"
+            >{{ $t('isSelf') }}</a-tag>
+            <a-tag
+              :visible="isShared(os)"
+              color="cyan"
+              @click="onFilterTag('is: shared')"
+            >{{ $t('isShared') }}</a-tag>
+          </a-radio>
+        </a-radio-group>
+      </a-list-item>
+    </a-list>
   </a-form-item>
 </template>
 
@@ -64,6 +73,45 @@ export default {
     inputDecorator: {
       type: String,
       default: ''
+    },
+    selected: {
+      type: String,
+      default: ''
+    },
+    itemCount: {
+      type: Number,
+      default: 0
+    }
+  },
+  data () {
+    return {
+      value: '',
+      page: 1,
+      pageSize: 10
+    }
+  },
+  created () {
+    this.value = this.selected
+    this.$emit('emit-update-template-iso', this.inputDecorator, this.value)
+  },
+  watch: {
+    inputDecorator (value) {
+      if (value === 'templateid') {
+        this.value = this.selected
+      }
+    }
+  },
+  computed: {
+    pagination () {
+      return {
+        size: 'small',
+        page: 1,
+        pageSize: 10,
+        total: this.itemCount,
+        showSizeChanger: true,
+        onChange: this.onChangePage,
+        onShowSizeChange: this.onChangePageSize
+      }
     }
   },
   methods: {
@@ -72,6 +120,22 @@ export default {
     },
     isSelf (item) {
       return !item.ispublic && (item.account === store.getters.userInfo.account)
+    },
+    updateSelectionTemplateIso (id) {
+      this.$emit('emit-update-template-iso', this.inputDecorator, id)
+    },
+    onChangePage (page, pageSize) {
+      this.pagination.page = page
+      this.pagination.pageSize = pageSize
+      this.$forceUpdate()
+    },
+    onChangePageSize (page, pageSize) {
+      this.pagination.page = page
+      this.pagination.pageSize = pageSize
+      this.$forceUpdate()
+    },
+    onFilterTag (tag) {
+      this.$emit('handle-filter-tag', tag)
     }
   }
 }
@@ -89,4 +153,14 @@ export default {
   .ant-tag {
     margin-left: 0.4rem;
   }
+
+  /deep/.ant-spin-container {
+    max-height: 200px;
+    overflow-y: auto;
+  }
+
+  .pagination {
+    margin-top: 20px;
+    float: right;
+  }
 </style>
diff --git a/src/views/compute/wizard/TemplateIsoSelection.vue b/src/views/compute/wizard/TemplateIsoSelection.vue
index 27796fe..b26f893 100644
--- a/src/views/compute/wizard/TemplateIsoSelection.vue
+++ b/src/views/compute/wizard/TemplateIsoSelection.vue
@@ -16,27 +16,33 @@
 // under the License.
 
 <template>
-  <a-tabs :defaultActiveKey="Object.keys(osTypes)[0]" v-if="view === TAB_VIEW">
-    <a-button icon="search" slot="tabBarExtraContent" @click="() => toggleView(FILTER_VIEW)"/>
-    <a-tab-pane v-for="(osList, osName) in osTypes" :key="osName">
-      <span slot="tab">
-        <os-logo :os-name="osName"></os-logo>
-      </span>
-      <TemplateIsoRadioGroup
-        :osList="osList"
-        :input-decorator="inputDecorator"
-      ></TemplateIsoRadioGroup>
-    </a-tab-pane>
-  </a-tabs>
-  <div v-else>
-    <a-input class="search-input" v-model="filter">
-      <a-icon slot="prefix" type="search"/>
-      <a-icon slot="addonAfter" type="close" @click="toggleView(TAB_VIEW)"/>
-    </a-input>
-    <TemplateIsoRadioGroup
-      :osList="filteredItems"
-      :input-decorator="inputDecorator"
-    ></TemplateIsoRadioGroup>
+  <div>
+    <a-input-search
+      class="search-input"
+      placeholder="Search"
+      v-model="filter"
+      @search="filterDataSource"/>
+    <a-spin :spinning="loading">
+      <a-tabs
+        tabPosition="top"
+        :animated="false"
+        :defaultActiveKey="Object.keys(dataSource)[0]">
+        <a-tab-pane v-for="(osList, osName) in dataSource" :key="osName">
+          <span slot="tab">
+            <os-logo :os-name="osName"></os-logo>
+          </span>
+          <TemplateIsoRadioGroup
+            :osType="osName"
+            :osList="dataSource[osName]"
+            :input-decorator="inputDecorator"
+            :selected="checkedValue"
+            :itemCount="itemCount[osName]"
+            @handle-filter-tag="filterDataSource"
+            @emit-update-template-iso="updateTemplateIso"
+          ></TemplateIsoRadioGroup>
+        </a-tab-pane>
+      </a-tabs>
+    </a-spin>
   </div>
 </template>
 
@@ -45,9 +51,7 @@ import OsLogo from '@/components/widgets/OsLogo'
 import { getNormalizedOsName } from '@/utils/icons'
 import _ from 'lodash'
 import TemplateIsoRadioGroup from '@views/compute/wizard/TemplateIsoRadioGroup'
-
-export const TAB_VIEW = 1
-export const FILTER_VIEW = 2
+import store from '@/store'
 
 export default {
   name: 'TemplateIsoSelection',
@@ -60,27 +64,53 @@ export default {
     inputDecorator: {
       type: String,
       default: ''
+    },
+    selected: {
+      type: String,
+      default: ''
+    },
+    loading: {
+      type: Boolean,
+      default: false
     }
   },
   data () {
     return {
-      TAB_VIEW: TAB_VIEW,
-      FILTER_VIEW: FILTER_VIEW,
-      visible: false,
       filter: '',
       filteredItems: this.items,
-      view: TAB_VIEW
+      checkedValue: '',
+      dataSource: {},
+      itemCount: {}
+    }
+  },
+  watch: {
+    items (items) {
+      this.filteredItems = []
+      this.checkedValue = ''
+      if (items && items.length > 0) {
+        this.filteredItems = items
+        this.checkedValue = items[0].id
+      }
+      this.dataSource = this.mappingDataSource()
+    },
+    inputDecorator (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        this.filter = ''
+      }
     }
   },
-  computed: {
-    osTypes () {
+  methods: {
+    mappingDataSource () {
       let mappedItems = {}
-      this.items.forEach((os) => {
+      const itemCount = {}
+      this.filteredItems.forEach((os) => {
         const osName = getNormalizedOsName(os.ostypename)
         if (Array.isArray(mappedItems[osName])) {
           mappedItems[osName].push(os)
+          itemCount[osName] = itemCount[osName] + 1
         } else {
           mappedItems[osName] = [os]
+          itemCount[osName] = 1
         }
       })
       mappedItems = _.mapValues(mappedItems, (list) => {
@@ -90,24 +120,50 @@ export default {
         nonFeaturedItems = _.sortBy(nonFeaturedItems, (item) => item.displaytext.toLowerCase())
         return featuredItems.concat(nonFeaturedItems) // pin featured isos/templates at the top
       })
+      this.itemCount = itemCount
       return mappedItems
-    }
-  },
-  watch: {
-    items (items) {
-      this.filteredItems = items
     },
-    filter (filterString) {
-      if (filterString !== '') {
-        this.filteredItems = this.filteredItems.filter((item) => item.displaytext.toLowerCase().includes(filterString))
+    updateTemplateIso (name, id) {
+      this.$emit('update-template-iso', name, id)
+    },
+    filterDataSource (strQuery) {
+      if (strQuery !== '' && strQuery.includes('is:')) {
+        this.filteredItems = []
+        this.filter = strQuery
+        const filters = strQuery.split(';')
+        filters.forEach((filter) => {
+          const query = filter.replace(/ /g, '')
+          const data = this.filterDataSourceByTag(query)
+          this.filteredItems = this.filteredItems.concat(data)
+        })
+      } else if (strQuery !== '') {
+        this.filteredItems = this.items.filter((item) => item.displaytext.toLowerCase().includes(strQuery.toLowerCase()))
       } else {
         this.filteredItems = this.items
       }
-    }
-  },
-  methods: {
-    toggleView (view) {
-      this.view = view
+      this.dataSource = this.mappingDataSource()
+    },
+    filterDataSourceByTag (tag) {
+      let arrResult = []
+      if (tag.includes('public')) {
+        arrResult = this.items.filter((item) => {
+          return item.ispublic && item.isfeatured
+        })
+      } else if (tag.includes('featured')) {
+        arrResult = this.items.filter((item) => {
+          return item.isfeatured
+        })
+      } else if (tag.includes('self')) {
+        arrResult = this.items.filter((item) => {
+          return !item.ispublic && (item.account === store.getters.userInfo.account)
+        })
+      } else if (tag.includes('shared')) {
+        arrResult = this.items.filter((item) => {
+          return !item.ispublic && (item.account !== store.getters.userInfo.account)
+        })
+      }
+
+      return arrResult
     }
   }
 }
@@ -115,6 +171,21 @@ export default {
 
 <style lang="less" scoped>
   .search-input {
-    margin: 0.5rem 0 1rem;
+    width: 25vw;
+    z-index: 8;
+    position: absolute;
+    top: 11px;
+    right: 10px;
+
+    @media (max-width: 600px) {
+      position: relative;
+      width: 100%;
+      top: 0;
+      right: 0;
+    }
+  }
+
+  /deep/.ant-tabs-nav-scroll {
+    min-height: 45px;
   }
 </style>
diff --git a/src/views/iam/SSLCertificateTab.vue b/src/views/iam/SSLCertificateTab.vue
index c5b2a79..a6941f0 100644
--- a/src/views/iam/SSLCertificateTab.vue
+++ b/src/views/iam/SSLCertificateTab.vue
@@ -81,7 +81,7 @@ export default {
       detailColumn: [],
       detail: [],
       page: 1,
-      pageSize: 20,
+      pageSize: 10,
       quickview: false,
       loading: false
     }
diff --git a/src/views/image/IsoZones.vue b/src/views/image/IsoZones.vue
index 5cb9582..b08bf99 100644
--- a/src/views/image/IsoZones.vue
+++ b/src/views/image/IsoZones.vue
@@ -70,7 +70,7 @@ export default {
       detailColumn: [],
       detail: [],
       page: 1,
-      pageSize: 20,
+      pageSize: 10,
       itemCount: 0,
       fetchLoading: false
     }
diff --git a/src/views/image/TemplateZones.vue b/src/views/image/TemplateZones.vue
index 5a86128..46661bb 100644
--- a/src/views/image/TemplateZones.vue
+++ b/src/views/image/TemplateZones.vue
@@ -68,7 +68,7 @@ export default {
       columns: [],
       dataSource: [],
       page: 1,
-      pageSize: 20,
+      pageSize: 10,
       itemCount: 0,
       fetchLoading: false
     }
diff --git a/src/views/network/VpcTab.vue b/src/views/network/VpcTab.vue
index 2b4d325..d0d8dbb 100644
--- a/src/views/network/VpcTab.vue
+++ b/src/views/network/VpcTab.vue
@@ -19,7 +19,7 @@
   <a-spin :spinning="fetchLoading">
     <a-tabs
       :activeKey="currentTab"
-      :tabPosition="device === 'tablet' || device === 'mobile' ? 'top' : 'left'"
+      :tabPosition="device === 'mobile' ? 'top' : 'left'"
       :animated="false"
       @change="handleChangeTab">
       <a-tab-pane :tab="$t('details')" key="details">