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/07/27 09:53:02 UTC

[cloudstack-primate] branch master updated: src: Consolidated Bug fixes (#539)

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 486a5b0  src: Consolidated Bug fixes (#539)
486a5b0 is described below

commit 486a5b0a67195fbabd00068e087b5559f6e1b279
Author: davidjumani <dj...@gmail.com>
AuthorDate: Mon Jul 27 15:22:53 2020 +0530

    src: Consolidated Bug fixes (#539)
    
    Fixes :
    - Don't allow users in UI to delete/archive events
    - Fix button name in VM deployment form in its network section to Create new network
    - Refresh after template / iso upload
    - Making external-id mandatory in ImportBackupOffering
    - Fixing visibility of assignVirtualMachineToBackupOffering
    - Removing link on traffic label
    - Defensive check in TrafficTypesTab
    - Ensuring we get the user info so that store.getters.user is never empty when the page is freshly loaded
    - Changing from report bug to report issue
    - Ordering projects in menu
    - Changing router get health check results
    - Show configureHAForHost based on hypervisor
    - Fix scale and migrate router
    - Fix scale and migrate systemvm
    - Fix show actionbutton for assignVirtualMachineToBackupOffering
    - Fix show actionbutton for stopKubernetesCluster
    - Fix show actions for volumes
    - Fix show actions for snapshots
    - Fix show actions for vm snapshots
    - Fix show actions for backups
    - Adding loading for tags and annotations
    - Enter to submit advanced search
    - Fixing show Project instead of account when passed as projectaccount passed in account field
    - Show project name instead of displaytext
    - Fixing template and iso actions
    - Fixing tags with projectid
    - Fix security groups ingress/egress rules view
    - Removing redundant allocationstate from zones
    - Adding managedstate to clusters
    - Adding capacity tab to clusters and pods
    - Adding routerlink to events in dashboard
    - Set autofocus to username in login
---
 src/components/header/ProjectMenu.vue              |  13 +-
 src/components/view/ActionButton.vue               |   4 +-
 src/components/view/DetailsTab.vue                 |  12 +-
 src/components/view/InfoCard.vue                   | 177 ++++++++++++---------
 src/components/view/ListView.vue                   |  20 +--
 src/components/view/SearchView.vue                 |   1 +
 src/config/section/compute.js                      |   4 +-
 src/config/section/event.js                        |   8 +-
 src/config/section/image.js                        |  48 +++---
 src/config/section/infra/clusters.js               |   5 +-
 src/config/section/infra/hosts.js                  |   1 +
 src/config/section/infra/pods.js                   |   7 +
 src/config/section/infra/routers.js                |   4 +
 src/config/section/infra/systemVms.js              |   8 +-
 src/config/section/infra/zones.js                  |   4 +-
 src/config/section/storage.js                      |  25 ++-
 src/locales/en.json                                |   6 +-
 src/store/modules/user.js                          |  16 +-
 src/utils/request.js                               |   6 +-
 src/views/auth/Login.vue                           |   1 +
 src/views/compute/wizard/NetworkSelection.vue      |   2 +-
 src/views/dashboard/CapacityDashboard.vue          |   2 +-
 src/views/dashboard/UsageDashboard.vue             |   2 +-
 src/views/image/IsoZones.vue                       |   8 +-
 src/views/image/RegisterOrUploadIso.vue            |   6 +-
 src/views/image/RegisterOrUploadTemplate.vue       |   5 +-
 src/views/image/TemplateZones.vue                  |   8 +-
 .../{zone/ZoneResources.vue => Resources.vue}      |   9 +-
 src/views/infra/network/TrafficTypesTab.vue        |   6 +-
 src/views/infra/routers/RouterHealthCheck.vue      |   2 +-
 src/views/network/IngressEgressRuleConfigure.vue   |  11 +-
 src/views/offering/ImportBackupOffering.vue        |   4 +-
 32 files changed, 272 insertions(+), 163 deletions(-)

diff --git a/src/components/header/ProjectMenu.vue b/src/components/header/ProjectMenu.vue
index 4e1314d..f55f963 100644
--- a/src/components/header/ProjectMenu.vue
+++ b/src/components/header/ProjectMenu.vue
@@ -48,6 +48,7 @@
 <script>
 import store from '@/store'
 import { api } from '@/api'
+import _ from 'lodash'
 
 export default {
   name: 'ProjectMenu',
@@ -66,20 +67,20 @@ export default {
         return
       }
       var page = 1
+      const projects = []
       const getNextPage = () => {
         this.loading = true
         api('listProjects', { listAll: true, details: 'min', page: page, pageSize: 500 }).then(json => {
-          if (page === 1) {
-            this.projects = [{ name: this.$t('label.default.view') }]
-          }
           if (json && json.listprojectsresponse && json.listprojectsresponse.project) {
-            this.projects.push(...json.listprojectsresponse.project)
+            projects.push(...json.listprojectsresponse.project)
           }
-          if (this.projects.length - 1 < json.listprojectsresponse.count) {
+          if (projects.length < json.listprojectsresponse.count) {
             page++
             getNextPage()
           }
         }).finally(() => {
+          this.projects = _.orderBy(projects, ['displaytext'], ['asc'])
+          this.projects.unshift({ name: this.$t('label.default.view') })
           this.loading = false
         })
       }
@@ -92,7 +93,7 @@ export default {
       const project = this.projects[index]
       this.$store.dispatch('SetProject', project)
       this.$store.dispatch('ToggleTheme', project.id === undefined ? 'light' : 'dark')
-      this.$message.success(`Switched to "${project.name}"`)
+      this.$message.success(`Switched to "${project.displaytext}"`)
       if (this.$route.name !== 'dashboard') {
         this.$router.push({ name: 'dashboard' })
       }
diff --git a/src/components/view/ActionButton.vue b/src/components/view/ActionButton.vue
index 596a919..6aa57a8 100644
--- a/src/components/view/ActionButton.vue
+++ b/src/components/view/ActionButton.vue
@@ -32,7 +32,7 @@
         :count="actionBadge[action.api] ? actionBadge[action.api].badgeNum : 0"
         v-if="action.api in $store.getters.apis &&
           action.showBadge && (
-            (!dataView && (action.listView || (action.groupAction && selectedRowKeys.length > 0))) ||
+            (!dataView && (action.listView || (action.groupAction && selectedRowKeys.length > 0 && ('groupShow' in action ? action.show(resource, $store.getters) : true)))) ||
             (dataView && action.dataView && ('show' in action ? action.show(resource, $store.getters) : true))
           )" >
         <a-button
@@ -49,7 +49,7 @@
       <a-button
         v-if="action.api in $store.getters.apis &&
           !action.showBadge && (
-            (!dataView && (action.listView || (action.groupAction && selectedRowKeys.length > 0))) ||
+            (!dataView && (action.listView || (action.groupAction && selectedRowKeys.length > 0 && ('groupShow' in action ? action.show(resource, $store.getters) : true)))) ||
             (dataView && action.dataView && ('show' in action ? action.show(resource, $store.getters) : true))
           )"
         :icon="action.icon"
diff --git a/src/components/view/DetailsTab.vue b/src/components/view/DetailsTab.vue
index f074118..12f623d 100644
--- a/src/components/view/DetailsTab.vue
+++ b/src/components/view/DetailsTab.vue
@@ -18,7 +18,7 @@
 <template>
   <a-list
     size="small"
-    :dataSource="$route.meta.details">
+    :dataSource="projectname ? [...$route.meta.details.filter(x => x !== 'account'), 'projectname'] : $route.meta.details">
     <a-list-item slot="renderItem" slot-scope="item" v-if="item in resource">
       <div>
         <strong>{{ item === 'service' ? $t('label.supportedservices') : $t('label.' + String(item).toLowerCase()) }}</strong>
@@ -73,7 +73,8 @@ export default {
   data () {
     return {
       dedicatedRoutes: ['zone', 'pod', 'cluster', 'host'],
-      dedicatedSectionActive: false
+      dedicatedSectionActive: false,
+      projectname: ''
     }
   },
   mounted () {
@@ -83,6 +84,13 @@ export default {
     this.dedicatedSectionActive = this.dedicatedRoutes.includes(this.$route.meta.name)
   },
   watch: {
+    resource (newItem) {
+      this.resource = newItem
+      if ('account' in this.resource && this.resource.account.startsWith('PrjAcct-')) {
+        this.projectname = this.resource.account.substring(this.resource.account.indexOf('-') + 1, this.resource.account.lastIndexOf('-'))
+        this.resource.projectname = this.projectname
+      }
+    },
     $route () {
       this.dedicatedSectionActive = this.dedicatedRoutes.includes(this.$route.meta.name)
     }
diff --git a/src/components/view/InfoCard.vue b/src/components/view/InfoCard.vue
index 0154f62..8e9a020 100644
--- a/src/components/view/InfoCard.vue
+++ b/src/components/view/InfoCard.vue
@@ -76,7 +76,7 @@
 
         <a-divider/>
 
-        <div class="resource-detail-item" v-if="resource.state || resource.status">
+        <div class="resource-detail-item" v-if="(resource.state || resource.status) && $route.meta.name !== 'zone'">
           <div class="resource-detail-item__label">{{ $t('label.status') }}</div>
           <div class="resource-detail-item__details">
             <status class="status" :text="resource.state || resource.status" displayText/>
@@ -288,11 +288,12 @@
             <span v-else>{{ resource.ipaddress }}</span>
           </div>
         </div>
-        <div class="resource-detail-item" v-if="resource.projectid">
+        <div class="resource-detail-item" v-if="resource.projectid || resource.projectname">
           <div class="resource-detail-item__label">{{ $t('label.project') }}</div>
           <div class="resource-detail-item__details">
             <a-icon type="project" />
-            <router-link :to="{ path: '/project/' + resource.projectid }">{{ resource.project || resource.projectname || resource.projectid }}</router-link>
+            <router-link v-if="resource.projectid" :to="{ path: '/project/' + resource.projectid }">{{ resource.project || resource.projectname || resource.projectid }}</router-link>
+            <router-link v-else :to="{ path: '/project', query: { name: resource.projectname }}">{{ resource.projectname }}</router-link>
           </div>
         </div>
 
@@ -458,7 +459,7 @@
             <span v-else>{{ resource.zone || resource.zonename || resource.zoneid }}</span>
           </div>
         </div>
-        <div class="resource-detail-item" v-if="resource.account">
+        <div class="resource-detail-item" v-if="resource.account && !resource.account.startsWith('PrjAcct-')">
           <div class="resource-detail-item__label">{{ $t('label.account') }}</div>
           <div class="resource-detail-item__details">
             <a-icon type="user" />
@@ -551,88 +552,89 @@
 
       <div class="account-center-tags" v-if="resourceType && 'listTags' in $store.getters.apis">
         <a-divider/>
-        <div class="title">{{ $t('label.tags') }}</div>
-        <div>
-          <template v-for="(tag, index) in tags">
-            <a-tag :key="index" :closable="'deleteTags' in $store.getters.apis" :afterClose="() => handleDeleteTag(tag)">
-              {{ tag.key }} = {{ tag.value }}
+        <a-spin :spinning="loadingTags">
+          <div class="title">{{ $t('label.tags') }}</div>
+          <div>
+            <template v-for="(tag, index) in tags">
+              <a-tag :key="index" :closable="isAdminOrOwner() && 'deleteTags' in $store.getters.apis" :afterClose="() => handleDeleteTag(tag)">
+                {{ tag.key }} = {{ tag.value }}
+              </a-tag>
+            </template>
+
+            <div v-if="inputVisible">
+              <a-input-group
+                type="text"
+                size="small"
+                @blur="handleInputConfirm"
+                @keyup.enter="handleInputConfirm"
+                compact>
+                <a-input ref="input" :value="inputKey" @change="handleKeyChange" style="width: 30%; text-align: center" :placeholder="$t('label.key')" />
+                <a-input style=" width: 30px; border-left: 0; pointer-events: none; backgroundColor: #fff" placeholder="=" disabled />
+                <a-input :value="inputValue" @change="handleValueChange" style="width: 30%; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
+                <a-button shape="circle" size="small" @click="handleInputConfirm">
+                  <a-icon type="check"/>
+                </a-button>
+                <a-button shape="circle" size="small" @click="inputVisible=false">
+                  <a-icon type="close"/>
+                </a-button>
+              </a-input-group>
+            </div>
+            <a-tag @click="showInput" style="background: #fff; borderStyle: dashed;" v-else-if="isAdminOrOwner() && 'createTags' in $store.getters.apis">
+              <a-icon type="plus" /> {{ $t('label.new.tag') }}
             </a-tag>
-          </template>
-
-          <div v-if="inputVisible">
-            <a-input-group
-              type="text"
-              size="small"
-              @blur="handleInputConfirm"
-              @keyup.enter="handleInputConfirm"
-              compact>
-              <a-input ref="input" :value="inputKey" @change="handleKeyChange" style="width: 30%; text-align: center" :placeholder="$t('label.key')" />
-              <a-input style=" width: 30px; border-left: 0; pointer-events: none; backgroundColor: #fff" placeholder="=" disabled />
-              <a-input :value="inputValue" @change="handleValueChange" style="width: 30%; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
-              <a-button shape="circle" size="small" @click="handleInputConfirm">
-                <a-icon type="check"/>
-              </a-button>
-              <a-button shape="circle" size="small" @click="inputVisible=false">
-                <a-icon type="close"/>
-              </a-button>
-            </a-input-group>
           </div>
-          <a-tag @click="showInput" style="background: #fff; borderStyle: dashed;" v-else-if="'createTags' in $store.getters.apis">
-            <a-icon type="plus" /> {{ $t('label.new.tag') }}
-          </a-tag>
-        </div>
+        </a-spin>
       </div>
 
       <div class="account-center-team" v-if="annotationType && 'listAnnotations' in $store.getters.apis">
         <a-divider :dashed="true"/>
-        <div class="title">
-          {{ $t('label.comments') }} ({{ notes.length }})
-        </div>
-        <a-list
-          v-if="notes.length"
-          :dataSource="notes"
-          itemLayout="horizontal"
-          size="small"
-        >
-          <a-list-item slot="renderItem" slot-scope="item">
-            <a-comment
-              :content="item.annotation"
-              :datetime="item.created"
-            >
+        <a-spin :spinning="loadingAnnotations">
+          <div class="title">
+            {{ $t('label.comments') }} ({{ notes.length }})
+          </div>
+          <a-list
+            v-if="notes.length"
+            :dataSource="notes"
+            itemLayout="horizontal"
+            size="small" >
+            <a-list-item slot="renderItem" slot-scope="item">
+              <a-comment
+                :content="item.annotation"
+                :datetime="item.created" >
+                <a-button
+                  v-if="'removeAnnotation' in $store.getters.apis"
+                  slot="avatar"
+                  type="danger"
+                  shape="circle"
+                  size="small"
+                  @click="deleteNote(item)">
+                  <a-icon type="delete"/>
+                </a-button>
+              </a-comment>
+            </a-list-item>
+          </a-list>
+
+          <a-comment v-if="'addAnnotation' in $store.getters.apis">
+            <a-avatar
+              slot="avatar"
+              icon="edit"
+              @click="showNotesInput = true" />
+            <div slot="content">
+              <a-textarea
+                rows="4"
+                @change="handleNoteChange"
+                :value="annotation"
+                :placeholder="$t('label.add.note')" />
               <a-button
-                v-if="'removeAnnotation' in $store.getters.apis"
-                slot="avatar"
-                type="danger"
-                shape="circle"
-                size="small"
-                @click="deleteNote(item)">
-                <a-icon type="delete"/>
+                style="margin-top: 10px"
+                @click="saveNote"
+                type="primary"
+              >
+                {{ $t('label.save') }}
               </a-button>
-            </a-comment>
-          </a-list-item>
-        </a-list>
-
-        <a-comment v-if="'addAnnotation' in $store.getters.apis">
-          <a-avatar
-            slot="avatar"
-            icon="edit"
-            @click="showNotesInput = true"
-          />
-          <div slot="content">
-            <a-textarea
-              rows="4"
-              @change="handleNoteChange"
-              :value="annotation"
-              :placeholder="$t('label.add.note')" />
-            <a-button
-              style="margin-top: 10px"
-              @click="saveNote"
-              type="primary"
-            >
-              {{ $t('label.save') }}
-            </a-button>
-          </div>
-        </a-comment>
+            </div>
+          </a-comment>
+        </a-spin>
       </div>
     </a-card>
   </a-spin>
@@ -681,7 +683,9 @@ export default {
       notes: [],
       annotation: '',
       showKeys: false,
-      showNotesInput: false
+      showNotesInput: false,
+      loadingTags: false,
+      loadingAnnotations: false
     }
   },
   watch: {
@@ -746,6 +750,7 @@ export default {
       if (!('listTags' in this.$store.getters.apis) || !this.resource || !this.resource.id) {
         return
       }
+      this.loadingTags = true
       this.tags = []
       const params = {
         listall: true,
@@ -759,19 +764,29 @@ export default {
         if (json.listtagsresponse && json.listtagsresponse.tag) {
           this.tags = json.listtagsresponse.tag
         }
+      }).finally(() => {
+        this.loadingTags = false
       })
     },
     getNotes () {
       if (!('listAnnotations' in this.$store.getters.apis)) {
         return
       }
+      this.loadingAnnotations = true
       this.notes = []
       api('listAnnotations', { entityid: this.resource.id, entitytype: this.annotationType }).then(json => {
         if (json.listannotationsresponse && json.listannotationsresponse.annotation) {
           this.notes = json.listannotationsresponse.annotation
         }
+      }).finally(() => {
+        this.loadingAnnotations = false
       })
     },
+    isAdminOrOwner () {
+      return ['Admin'].includes(this.$store.getters.userInfo.roletype) ||
+        (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
+        this.resource.project && this.resource.projectid === this.$store.getters.project.id
+    },
     showInput () {
       this.inputVisible = true
       this.$nextTick(function () {
@@ -786,6 +801,7 @@ export default {
     },
     handleInputConfirm () {
       const args = {}
+      this.loadingTags = true
       args.resourceids = this.resource.id
       args.resourcetype = this.resourceType
       args['tags[0].key'] = this.inputKey
@@ -801,6 +817,7 @@ export default {
     },
     handleDeleteTag (tag) {
       const args = {}
+      this.loadingTags = true
       args.resourceids = this.resource.id
       args.resourcetype = this.resourceType
       args['tags[0].key'] = tag.key
@@ -817,6 +834,7 @@ export default {
       if (this.annotation.length < 1) {
         return
       }
+      this.loadingAnnotations = true
       this.showNotesInput = false
       const args = {}
       args.entityid = this.resource.id
@@ -829,6 +847,7 @@ export default {
       this.annotation = ''
     },
     deleteNote (annotation) {
+      this.loadingAnnotations = true
       const args = {}
       args.id = annotation.id
       api('removeAnnotation', args).then(json => {
diff --git a/src/components/view/ListView.vue b/src/components/view/ListView.vue
index 02048ab..fe0740d 100644
--- a/src/components/view/ListView.vue
+++ b/src/components/view/ListView.vue
@@ -104,9 +104,9 @@
     <a slot="publicip" slot-scope="text, record" href="javascript:;">
       <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
     </a>
-    <a slot="traffictype" slot-scope="text, record" href="javascript:;">
-      <router-link :to="{ path: $route.path + '/' + record.id + '?physicalnetworkid=' + record.physicalnetworkid }">{{ text }}</router-link>
-    </a>
+    <span slot="traffictype" slot-scope="text" href="javascript:;">
+      {{ text }}
+    </span>
     <a slot="vmname" slot-scope="text, record" href="javascript:;">
       <router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
     </a>
@@ -164,12 +164,14 @@
       <router-link :to="{ path: '/pod/' + record.podid }">{{ text }}</router-link>
     </a>
     <span slot="account" slot-scope="text, record">
-      <router-link
-        v-if="'quota' in record && $router.resolve(`${$route.path}/${record.account}`) !== '404'"
-        :to="{ path: `${$route.path}/${record.account}`, query: { account: record.account, domainid: record.domainid, quota: true } }">{{ text }}</router-link>
-      <router-link :to="{ path: '/account/' + record.accountid }" v-else-if="record.accountid">{{ text }}</router-link>
-      <router-link :to="{ path: '/account', query: { name: record.account, domainid: record.domainid } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
-      <span v-else>{{ text }}</span>
+      <template v-if="text && !text.startsWith('PrjAcct-')">
+        <router-link
+          v-if="'quota' in record && $router.resolve(`${$route.path}/${record.account}`) !== '404'"
+          :to="{ path: `${$route.path}/${record.account}`, query: { account: record.account, domainid: record.domainid, quota: true } }">{{ text }}</router-link>
+        <router-link :to="{ path: '/account/' + record.accountid }" v-else-if="record.accountid">{{ text }}</router-link>
+        <router-link :to="{ path: '/account', query: { name: record.account, domainid: record.domainid } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
+      </template>
     </span>
     <span slot="domain" slot-scope="text, record" href="javascript:;">
       <router-link v-if="record.domainid && !record.domainid.toString().includes(',') && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + record.domainid }">{{ text }}</router-link>
diff --git a/src/components/view/SearchView.vue b/src/components/view/SearchView.vue
index 7319a8c..d7084e5 100644
--- a/src/components/view/SearchView.vue
+++ b/src/components/view/SearchView.vue
@@ -91,6 +91,7 @@
                   type="primary"
                   size="small"
                   icon="search"
+                  html-type="submit"
                   @click="handleSubmit">{{ $t('label.search') }}</a-button>
               </div>
             </a-form>
diff --git a/src/config/section/compute.js b/src/config/section/compute.js
index 2e0723e..6a9dc58 100644
--- a/src/config/section/compute.js
+++ b/src/config/section/compute.js
@@ -177,7 +177,7 @@ export default {
           docHelp: 'adminguide/virtual_machines.html#backup-offerings',
           dataView: true,
           args: ['virtualmachineid', 'backupofferingid'],
-          show: (record) => { return !record.backupofferingid && !['Error', 'Destroyed'].includes(record.state) && ['VMware', 'Simulator'].includes(record.hypervisor) },
+          show: (record) => { return !record.backupofferingid },
           mapping: {
             virtualmachineid: {
               value: (record, params) => { return record.id }
@@ -441,7 +441,7 @@ export default {
           label: 'label.kubernetes.cluster.stop',
           docHelp: 'plugins/cloudstack-kubernetes-service.html#stopping-kubernetes-cluster',
           dataView: true,
-          show: (record) => { return !['Stopped'].includes(record.state) }
+          show: (record) => { return !['Stopped', 'Destroyed', 'Destroying'].includes(record.state) }
         },
         {
           api: 'scaleKubernetesCluster',
diff --git a/src/config/section/event.js b/src/config/section/event.js
index b045332..1411fb0 100644
--- a/src/config/section/event.js
+++ b/src/config/section/event.js
@@ -44,7 +44,9 @@ export default {
         ids: {
           value: (record) => { return record.id }
         }
-      }
+      },
+      show: (record, store) => { return !['User'].includes(store.userInfo.roletype) },
+      groupShow: (record, store) => { return !['User'].includes(store.userInfo.roletype) }
     },
     {
       api: 'deleteEvents',
@@ -60,7 +62,9 @@ export default {
         ids: {
           value: (record) => { return record.id }
         }
-      }
+      },
+      show: (record, store) => { return !['User'].includes(store.userInfo.roletype) },
+      groupShow: (record, store) => { return !['User'].includes(store.userInfo.roletype) }
     }
   ]
 }
diff --git a/src/config/section/image.js b/src/config/section/image.js
index 072121a..d8ad4cd 100644
--- a/src/config/section/image.js
+++ b/src/config/section/image.js
@@ -85,8 +85,9 @@ export default {
           label: 'label.edit',
           dataView: true,
           show: (record, store) => {
-            return (['Admin'].includes(store.userInfo.roletype) ||
-              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account)) &&
+            return (['Admin'].includes(store.userInfo.roletype) || // If admin or owner or belongs to current project
+              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) ||
+              (record.domainid === store.userInfo.domainid && record.projectid && store.project && store.project.id && record.projectid === store.project.id)) &&
               record.templatetype !== 'SYSTEM' &&
               record.isready
           },
@@ -105,8 +106,9 @@ export default {
           dataView: true,
           args: ['ispublic', 'isfeatured', 'isextractable'],
           show: (record, store) => {
-            return (['Admin'].includes(store.userInfo.roletype) ||
-              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account)) &&
+            return (['Admin'].includes(store.userInfo.roletype) || // If admin or owner or belongs to current project
+              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) ||
+              (record.domainid === store.userInfo.domainid && record.projectid && store.project && store.project.id && record.projectid === store.project.id)) &&
               record.templatetype !== 'SYSTEM' &&
               record.isready
           }
@@ -119,8 +121,9 @@ export default {
           docHelp: 'adminguide/templates.html#exporting-templates',
           dataView: true,
           show: (record, store) => {
-            return (['Admin'].includes(store.userInfo.roletype) ||
-              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account)) &&
+            return (['Admin'].includes(store.userInfo.roletype) || // If admin or owner or belongs to current project
+              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) ||
+              (record.domainid === store.userInfo.domainid && record.projectid && store.project && store.project.id && record.projectid === store.project.id)) &&
               record.templatetype !== 'SYSTEM' &&
               record.isready &&
               record.isextractable
@@ -144,8 +147,9 @@ export default {
           dataView: true,
           popup: true,
           show: (record, store) => {
-            return (['Admin'].includes(store.userInfo.roletype) ||
-              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account)) &&
+            return (['Admin'].includes(store.userInfo.roletype) || // If admin or owner or belongs to current project
+              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) ||
+              (record.domainid === store.userInfo.domainid && record.projectid && store.project && store.project.id && record.projectid === store.project.id)) &&
               record.templatetype !== 'SYSTEM' &&
               record.isready
           },
@@ -208,9 +212,10 @@ export default {
           label: 'label.action.edit.iso',
           dataView: true,
           show: (record, store) => {
-            return (['Admin'].includes(store.userInfo.roletype) ||
-              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account)) &&
-              !(record.account === 'SYSTEM' && record.domainid === 1) &&
+            return (['Admin'].includes(store.userInfo.roletype) || // If admin or owner or belongs to current project
+              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) ||
+              (record.domainid === store.userInfo.domainid && record.projectid && store.project && store.project.id && record.projectid === store.project.id)) &&
+              !(record.account === 'system' && record.domainid === 1) &&
               record.isready
           },
           args: ['name', 'displaytext', 'bootable', 'ostypeid']
@@ -222,9 +227,10 @@ export default {
           dataView: true,
           args: ['ispublic', 'isfeatured', 'isextractable'],
           show: (record, store) => {
-            return (['Admin'].includes(store.userInfo.roletype) ||
-              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account)) &&
-              !(record.account === 'SYSTEM' && record.domainid === 1) &&
+            return (['Admin'].includes(store.userInfo.roletype) || // If admin or owner or belongs to current project
+              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) ||
+              (record.domainid === store.userInfo.domainid && record.projectid && store.project && store.project.id && record.projectid === store.project.id)) &&
+              !(record.account === 'system' && record.domainid === 1) &&
               record.isready
           }
         },
@@ -236,9 +242,10 @@ export default {
           docHelp: 'adminguide/templates.html#exporting-templates',
           dataView: true,
           show: (record, store) => {
-            return (['Admin'].includes(store.userInfo.roletype) ||
-              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account)) &&
-              !(record.account === 'SYSTEM' && record.domainid === 1) &&
+            return (['Admin'].includes(store.userInfo.roletype) || // If admin or owner or belongs to current project
+              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) ||
+              (record.domainid === store.userInfo.domainid && record.projectid && store.project && store.project.id && record.projectid === store.project.id)) &&
+              !(record.account === 'system' && record.domainid === 1) &&
               record.isready
           },
           args: ['zoneid', 'mode'],
@@ -261,9 +268,10 @@ export default {
           args: ['op', 'accounts', 'projectids'],
           popup: true,
           show: (record, store) => {
-            return (['Admin'].includes(store.userInfo.roletype) ||
-              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account)) &&
-              !(record.account === 'SYSTEM' && record.domainid === 1) &&
+            return (['Admin'].includes(store.userInfo.roletype) || // If admin or owner or belongs to current project
+              (record.domainid === store.userInfo.domainid && record.account === store.userInfo.account) ||
+              (record.domainid === store.userInfo.domainid && record.projectid && store.project && store.project.id && record.projectid === store.project.id)) &&
+              !(record.account === 'system' && record.domainid === 1) &&
               record.isready
           },
           component: () => import('@/views/image/UpdateTemplateIsoPermissions')
diff --git a/src/config/section/infra/clusters.js b/src/config/section/infra/clusters.js
index 98f68a6..fcb13b0 100644
--- a/src/config/section/infra/clusters.js
+++ b/src/config/section/infra/clusters.js
@@ -32,7 +32,7 @@ export default {
     fields.push('zonename')
     return fields
   },
-  details: ['name', 'id', 'allocationstate', 'clustertype', 'hypervisortype', 'podname', 'zonename'],
+  details: ['name', 'id', 'allocationstate', 'clustertype', 'managedstate', 'hypervisortype', 'podname', 'zonename'],
   related: [{
     name: 'host',
     title: 'label.hosts',
@@ -44,6 +44,9 @@ export default {
   }, {
     name: 'settings',
     component: () => import('@/components/view/SettingsTab.vue')
+  }, {
+    name: 'resources',
+    component: () => import('@/views/infra/Resources.vue')
   }],
   actions: [
     {
diff --git a/src/config/section/infra/hosts.js b/src/config/section/infra/hosts.js
index 8ddbef5..06e690b 100644
--- a/src/config/section/infra/hosts.js
+++ b/src/config/section/infra/hosts.js
@@ -220,6 +220,7 @@ export default {
       message: 'label.ha.configure',
       docHelp: 'adminguide/reliability.html#ha-for-hosts',
       dataView: true,
+      show: (record) => { return ['KVM', 'Simulator'].includes(record.hypervisor) },
       args: ['hostid', 'provider'],
       mapping: {
         hostid: {
diff --git a/src/config/section/infra/pods.js b/src/config/section/infra/pods.js
index 107fcfa..dbc9791 100644
--- a/src/config/section/infra/pods.js
+++ b/src/config/section/infra/pods.js
@@ -31,6 +31,13 @@ export default {
     title: 'label.hosts',
     param: 'podid'
   }],
+  tabs: [{
+    name: 'details',
+    component: () => import('@/components/view/DetailsTab.vue')
+  }, {
+    name: 'resources',
+    component: () => import('@/views/infra/Resources.vue')
+  }],
   actions: [
     {
       api: 'createPod',
diff --git a/src/config/section/infra/routers.js b/src/config/section/infra/routers.js
index 91f7357..57e12cf 100644
--- a/src/config/section/infra/routers.js
+++ b/src/config/section/infra/routers.js
@@ -103,6 +103,10 @@ export default {
       mapping: {
         virtualmachineid: {
           value: (record) => { return record.id }
+        },
+        hostid: {
+          api: 'findHostsForMigration',
+          params: (record) => { return { virtualmachineid: record.id } }
         }
       }
     },
diff --git a/src/config/section/infra/systemVms.js b/src/config/section/infra/systemVms.js
index 1834ade..8b3c66a 100644
--- a/src/config/section/infra/systemVms.js
+++ b/src/config/section/infra/systemVms.js
@@ -55,12 +55,12 @@ export default {
       label: 'label.change.service.offering',
       message: 'message.confirm.scale.up.system.vm',
       dataView: true,
-      show: (record) => { return record.hypervisor !== 'KVM' },
+      show: (record) => { return record.state === 'Running' && record.hypervisor === 'VMware' || record.state === 'Stopped' },
       args: ['serviceofferingid'],
       mapping: {
         serviceofferingid: {
           api: 'listServiceOfferings',
-          params: (record) => { return { virtualmachineid: record.virtualmachineid, issystem: true, systemvmtype: record.systemvmtype } }
+          params: (record) => { return { virtualmachineid: record.id, issystem: true, systemvmtype: record.systemvmtype } }
         }
       }
     },
@@ -74,6 +74,10 @@ export default {
       mapping: {
         virtualmachineid: {
           value: (record) => { return record.id }
+        },
+        hostid: {
+          api: 'findHostsForMigration',
+          params: (record) => { return { virtualmachineid: record.id } }
         }
       }
     },
diff --git a/src/config/section/infra/zones.js b/src/config/section/infra/zones.js
index ecfcce1..af71998 100644
--- a/src/config/section/infra/zones.js
+++ b/src/config/section/infra/zones.js
@@ -23,7 +23,7 @@ export default {
   icon: 'global',
   permission: ['listZonesMetrics'],
   columns: () => {
-    const fields = ['name', 'state', 'allocationstate', 'networktype', 'clusters']
+    const fields = ['name', 'allocationstate', 'networktype', 'clusters']
     const metricsFields = ['cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal']
     if (store.getters.metrics) {
       fields.push(...metricsFields)
@@ -64,7 +64,7 @@ export default {
     component: () => import('@/views/infra/zone/SystemVmsTab.vue')
   }, {
     name: 'resources',
-    component: () => import('@/views/infra/zone/ZoneResources.vue')
+    component: () => import('@/views/infra/Resources.vue')
   }, {
     name: 'settings',
     component: () => import('@/components/view/SettingsTab.vue')
diff --git a/src/config/section/storage.js b/src/config/section/storage.js
index b7c2070..0cf1927 100644
--- a/src/config/section/storage.js
+++ b/src/config/section/storage.js
@@ -102,7 +102,7 @@ export default {
           message: 'message.confirm.attach.disk',
           args: ['virtualmachineid'],
           dataView: true,
-          show: (record) => { return record.type !== 'ROOT' && record.state !== 'Destroy' && !('virtualmachineid' in record) }
+          show: (record) => { return record.type !== 'ROOT' && ['Allocated', 'Ready', 'Uploaded'].includes(record.state) && !('virtualmachineid' in record) }
         },
         {
           api: 'detachVolume',
@@ -110,7 +110,10 @@ export default {
           label: 'label.action.detach.disk',
           message: 'message.detach.disk',
           dataView: true,
-          show: (record) => { return record.type !== 'ROOT' && 'virtualmachineid' in record && record.virtualmachineid }
+          show: (record) => {
+            return record.type !== 'ROOT' && 'virtualmachineid' in record && record.virtualmachineid &&
+              ['Running', 'Stopped', 'Destroyed'].includes(record.vmstate)
+          }
         },
         {
           api: 'createSnapshot',
@@ -147,7 +150,7 @@ export default {
           label: 'label.action.resize.volume',
           dataView: true,
           popup: true,
-          show: (record) => { return record.state !== 'Destroy' },
+          show: (record) => { return ['Allocated', 'Ready'].includes(record.state) },
           component: () => import('@/views/storage/ResizeVolume.vue')
         },
         {
@@ -167,7 +170,7 @@ export default {
           label: 'label.action.download.volume',
           message: 'message.download.volume.confirm',
           dataView: true,
-          show: (record) => { return record && record.state === 'Ready' && (record.vmstate === 'Stopped' || record.virtualmachineid == null) && record.state !== 'Destroy' },
+          show: (record) => { return record && record.state === 'Ready' && (record.vmstate === 'Stopped' || record.virtualmachineid == null) },
           args: ['zoneid', 'mode'],
           mapping: {
             zoneid: {
@@ -278,14 +281,15 @@ export default {
           label: 'label.action.revert.snapshot',
           message: 'message.action.revert.snapshot',
           dataView: true,
-          show: (record) => { return record.revertable }
+          show: (record) => { return record.state === 'BackedUp' && record.revertable }
         },
         {
           api: 'deleteSnapshot',
           icon: 'delete',
           label: 'label.action.delete.snapshot',
           message: 'message.action.delete.snapshot',
-          dataView: true
+          dataView: true,
+          show: (record) => { return record.state !== 'Destroyed' }
         }
       ]
     },
@@ -326,6 +330,7 @@ export default {
           label: 'label.action.vmsnapshot.delete',
           message: 'message.action.vmsnapshot.delete',
           dataView: true,
+          show: (record) => { return ['Ready', 'Expunging', 'Error'].includes(record.state) },
           args: ['vmsnapshotid'],
           mapping: {
             vmsnapshotid: {
@@ -349,7 +354,8 @@ export default {
           docHelp: 'adminguide/virtual_machines.html#restoring-vm-backups',
           label: 'label.backup.restore',
           message: 'message.backup.restore',
-          dataView: true
+          dataView: true,
+          show: (record) => { return record.state !== 'Destroyed' }
         },
         {
           api: 'restoreVolumeFromBackupAndAttachToVM',
@@ -357,6 +363,7 @@ export default {
           label: 'label.backup.attach.restore',
           message: 'message.backup.attach.restore',
           dataView: true,
+          show: (record) => { return record.state !== 'Destroyed' },
           popup: true,
           component: () => import('@/views/storage/RestoreAttachBackupVolume.vue')
         },
@@ -366,6 +373,7 @@ export default {
           label: 'label.backup.offering.remove',
           message: 'message.backup.offering.remove',
           dataView: true,
+          show: (record) => { return record.state !== 'Destroyed' },
           args: ['forced', 'virtualmachineid'],
           mapping: {
             forced: {
@@ -381,7 +389,8 @@ export default {
           icon: 'delete',
           label: 'label.delete.backup',
           message: 'message.delete.backup',
-          dataView: true
+          dataView: true,
+          show: (record) => { return record.state !== 'Destroyed' }
         }
       ]
     }
diff --git a/src/locales/en.json b/src/locales/en.json
index 3277611..f0831d8 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -46,7 +46,7 @@
 "label.access": "Access",
 "label.accesskey": "Access Key",
 "label.account": "Account",
-"label.account.and.security.group": "Account, Security group",
+"label.account.and.security.group": "Account - Security group",
 "label.account.details": "Account details",
 "label.account.id": "Account ID",
 "label.account.name": "Account Name",
@@ -602,6 +602,7 @@
 "label.cpuusedghz": "Used CPU",
 "label.create.account": "Create Account",
 "label.create.backup": "Start Backup",
+"label.create.network": "Create New Network",
 "label.create.network.gateway.description": "The gateway of the tier in the super CIDR range and not overlapping the CIDR of any other tier in this VPC.",
 "label.create.network.netmask.description": "Netmask of the tier. For example, with VPC CIDR of 10.0.0.0/16 and network tier CIDR of 10.1.1.0/24, gateway is 10.1.1.1 and netmask is 255.255.255.0",
 "label.create.nfs.secondary.staging.storage": "Create NFS Secondary Staging Store",
@@ -1584,6 +1585,7 @@
 "label.profiledn": "Associated Profile",
 "label.profilename": "Profile",
 "label.project": "Project",
+"label.projectname": "Project",
 "label.project.dashboard": "Project dashboard",
 "label.project.ids": "Project IDs",
 "label.project.invitation": "Project Invitations",
@@ -1724,7 +1726,7 @@
 "label.removing.user": "Removing User",
 "label.replace.acl": "Replace ACL",
 "label.replace.acl.list": "Replace ACL List",
-"label.report.bug": "Report Bug",
+"label.report.bug": "Report Issue",
 "label.required": "Required",
 "label.requireshvm": "HVM",
 "label.requiresupgrade": "Requires Upgrade",
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index abb57e5..3fa46da 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -123,7 +123,21 @@ const user = {
         if (hasAuth) {
           console.log('Login detected, using cached APIs')
           commit('SET_APIS', cachedApis)
-          resolve(cachedApis)
+
+          // Ensuring we get the user info so that store.getters.user is never empty when the page is freshly loaded
+          api('listUsers', { username: Cookies.get('username'), listall: true }).then(response => {
+            const result = response.listusersresponse.user[0]
+            commit('SET_INFO', result)
+            commit('SET_NAME', result.firstname + ' ' + result.lastname)
+            if ('email' in result) {
+              commit('SET_AVATAR', 'https://www.gravatar.com/avatar/' + md5(result.email))
+            } else {
+              commit('SET_AVATAR', 'https://www.gravatar.com/avatar/' + md5('dev@cloudstack.apache.org'))
+            }
+            resolve(cachedApis)
+          }).catch(error => {
+            reject(error)
+          })
         } else {
           const hide = message.loading(i18n.t('message.discovering.feature'), 0)
           api('listApis').then(response => {
diff --git a/src/utils/request.js b/src/utils/request.js
index 382dcfd..0abd090 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -72,7 +72,11 @@ service.interceptors.request.use(config => {
     config.params.response = 'json'
     const project = Vue.ls.get(CURRENT_PROJECT)
     if (!config.params.projectid && project && project.id) {
-      config.params.projectid = project.id
+      if (config.params.command === 'listTags') {
+        config.params.projectid = '-1'
+      } else {
+        config.params.projectid = project.id
+      }
     }
   }
   return config
diff --git a/src/views/auth/Login.vue b/src/views/auth/Login.vue
index aa53f53..5f95872 100644
--- a/src/views/auth/Login.vue
+++ b/src/views/auth/Login.vue
@@ -39,6 +39,7 @@
           <a-input
             size="large"
             type="text"
+            autoFocus
             :placeholder="$t('label.username')"
             v-decorator="[
               'username',
diff --git a/src/views/compute/wizard/NetworkSelection.vue b/src/views/compute/wizard/NetworkSelection.vue
index 4a011d0..85d3c14 100644
--- a/src/views/compute/wizard/NetworkSelection.vue
+++ b/src/views/compute/wizard/NetworkSelection.vue
@@ -23,7 +23,7 @@
       v-model="filter"
       @search="handleSearch" />
     <a-button type="primary" @click="showCreateForm = true" style="float: right; margin-right: 5px; z-index: 8">
-      {{ $t('label.add.network') }}
+      {{ $t('label.create.network') }}
     </a-button>
     <a-table
       :loading="loading"
diff --git a/src/views/dashboard/CapacityDashboard.vue b/src/views/dashboard/CapacityDashboard.vue
index 07bb14e..6c01044 100644
--- a/src/views/dashboard/CapacityDashboard.vue
+++ b/src/views/dashboard/CapacityDashboard.vue
@@ -109,7 +109,7 @@
                 :key="event.id"
                 :color="getEventColour(event)">
                 <span :style="{ color: '#999' }"><small>{{ event.created }}</small></span><br/>
-                <span :style="{ color: '#666' }"><small>{{ $t(event.type.toLowerCase()) }}</small></span><br/>
+                <span :style="{ color: '#666' }"><small><router-link :to="{ path: 'event/' + event.id }">{{ event.type }}</router-link></small></span><br/>
                 <span :style="{ color: '#aaa' }">({{ event.username }}) {{ event.description }}</span>
               </a-timeline-item>
             </a-timeline>
diff --git a/src/views/dashboard/UsageDashboard.vue b/src/views/dashboard/UsageDashboard.vue
index a448032..b29fc8d 100644
--- a/src/views/dashboard/UsageDashboard.vue
+++ b/src/views/dashboard/UsageDashboard.vue
@@ -82,7 +82,7 @@
                 :key="event.id"
                 :color="getEventColour(event)">
                 <span :style="{ color: '#999' }"><small>{{ event.created }}</small></span><br/>
-                <span :style="{ color: '#666' }"><small>{{ event.type }}</small></span><br/>
+                <span :style="{ color: '#666' }"><small><router-link :to="{ path: 'event/' + event.id }">{{ event.type }}</router-link></small></span><br/>
                 <span :style="{ color: '#aaa' }">({{ event.username }}) {{ event.description }}</span>
               </a-timeline-item>
             </a-timeline>
diff --git a/src/views/image/IsoZones.vue b/src/views/image/IsoZones.vue
index 91db6c5..89351f7 100644
--- a/src/views/image/IsoZones.vue
+++ b/src/views/image/IsoZones.vue
@@ -230,9 +230,11 @@ export default {
       this.fetchData()
     },
     isActionPermitted () {
-      return (['Admin'].includes(this.$store.getters.userInfo.roletype) ||
-        (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account)) &&
-        !(this.resource.account !== 'SYSTEM' && this.resource.domainid === 1)
+      return (['Admin'].includes(this.$store.getters.userInfo.roletype) || // If admin or owner or belongs to current project
+        (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
+        (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.projectid && this.$store.getters.project && this.$store.getters.project.id && this.resource.projectid === this.$store.getters.project.id)) &&
+        (this.resource.isready || !this.resource.status || this.resource.status.indexOf('Downloaded') === -1) && // Iso is ready or downloaded
+        this.resource.account !== 'system'
     },
     deleteIso (record) {
       const params = {
diff --git a/src/views/image/RegisterOrUploadIso.vue b/src/views/image/RegisterOrUploadIso.vue
index 96fe426..f632fc6 100644
--- a/src/views/image/RegisterOrUploadIso.vue
+++ b/src/views/image/RegisterOrUploadIso.vue
@@ -293,14 +293,15 @@ export default {
           message: this.$t('message.success.upload'),
           description: this.$t('message.success.upload.description')
         })
-        this.closeAction()
       }).catch(e => {
         this.$notification.error({
           message: this.$t('message.upload.failed'),
           description: `${this.$t('message.upload.iso.failed.description')} -  ${e}`,
           duration: 0
         })
+      }).finally(() => {
         this.closeAction()
+        this.$emit('refresh-data')
       })
     },
     handleSubmit (e) {
@@ -336,7 +337,6 @@ export default {
         if (this.currentForm === 'Create') {
           this.loading = true
           api('registerIso', params).then(json => {
-            this.$emit('refresh-data')
             this.$notification.success({
               message: 'label.action.register.iso',
               description: `${this.$t('message.success.register.iso')} ${params.name}`
@@ -346,6 +346,7 @@ export default {
           }).finally(() => {
             this.loading = false
             this.closeAction()
+            this.$emit('refresh-data')
           })
         } else {
           if (this.fileList.length !== 1) {
@@ -366,6 +367,7 @@ export default {
             this.$notifyError(error)
           }).finally(() => {
             this.loading = false
+            this.$emit('refresh-data')
           })
         }
       })
diff --git a/src/views/image/RegisterOrUploadTemplate.vue b/src/views/image/RegisterOrUploadTemplate.vue
index 9ee0348..f0ad376 100644
--- a/src/views/image/RegisterOrUploadTemplate.vue
+++ b/src/views/image/RegisterOrUploadTemplate.vue
@@ -479,13 +479,14 @@ export default {
           message: this.$t('message.success.upload'),
           description: this.$t('message.success.upload.template.description')
         })
-        this.closeAction()
       }).catch(e => {
         this.$notification.error({
           message: this.$t('message.upload.failed'),
           description: `${this.$t('message.upload.template.failed.description')} -  ${e}`,
           duration: 0
         })
+      }).finally(() => {
+        this.$emit('refresh-data')
         this.closeAction()
       })
     },
@@ -852,7 +853,6 @@ export default {
         if (this.currentForm === 'Create') {
           this.loading = true
           api('registerTemplate', params).then(json => {
-            this.$emit('refresh-data')
             this.$notification.success({
               message: this.$t('label.register.template'),
               description: `${this.$t('message.success.register.template')} ${params.name}`
@@ -861,6 +861,7 @@ export default {
             this.$notifyError(error)
           }).finally(() => {
             this.loading = false
+            this.$emit('refresh-data')
             this.closeAction()
           })
         } else {
diff --git a/src/views/image/TemplateZones.vue b/src/views/image/TemplateZones.vue
index e8b0a1c..00e7af2 100644
--- a/src/views/image/TemplateZones.vue
+++ b/src/views/image/TemplateZones.vue
@@ -240,9 +240,11 @@ export default {
       this.fetchData()
     },
     isActionPermitted () {
-      return (['Admin'].includes(this.$store.getters.userInfo.roletype) ||
-        (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account)) &&
-        this.resource.isready && this.resource.templatetype !== 'SYSTEM'
+      return (['Admin'].includes(this.$store.getters.userInfo.roletype) || // If admin or owner or belongs to current project
+        (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
+        (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.projectid && this.$store.getters.project && this.$store.getters.project.id && this.resource.projectid === this.$store.getters.project.id)) &&
+        (this.resource.isready || !this.resource.status || this.resource.status.indexOf('Downloaded') === -1) && // Template is ready or downloaded
+        this.resource.templatetype !== 'SYSTEM'
     },
     deleteTemplate () {
       const params = {
diff --git a/src/views/infra/zone/ZoneResources.vue b/src/views/infra/Resources.vue
similarity index 96%
rename from src/views/infra/zone/ZoneResources.vue
rename to src/views/infra/Resources.vue
index 0531fea..aba42ef 100644
--- a/src/views/infra/zone/ZoneResources.vue
+++ b/src/views/infra/Resources.vue
@@ -41,7 +41,7 @@
 import { api } from '@/api'
 
 export default {
-  name: 'ZoneResources',
+  name: 'Resources',
   props: {
     resource: {
       type: Object,
@@ -66,10 +66,11 @@ export default {
   },
   methods: {
     fetchData () {
+      const entity = this.$route.meta.name + 'id'
+      const params = {}
+      params[entity] = this.resource.id
       this.fetchLoading = true
-      api('listCapacity', {
-        zoneid: this.resource.id
-      }).then(response => {
+      api('listCapacity', params).then(response => {
         this.resourcesList = response.listcapacityresponse.capacity
         this.animatePercentVals()
       }).catch(error => {
diff --git a/src/views/infra/network/TrafficTypesTab.vue b/src/views/infra/network/TrafficTypesTab.vue
index 33b04eb..300511c 100644
--- a/src/views/infra/network/TrafficTypesTab.vue
+++ b/src/views/infra/network/TrafficTypesTab.vue
@@ -111,7 +111,11 @@ export default {
         isSystem: true,
         zoneId: this.resource.zoneid
       }).then(json => {
-        this.publicNetwork = json.listnetworksresponse.network[0] || {}
+        if (json.listnetworksresponse && json.listnetworksresponse.network && json.listnetworksresponse.network.length > 0) {
+          this.publicNetwork = json.listnetworksresponse.network[0]
+        } else {
+          this.publicNetwork = {}
+        }
       }).catch(error => {
         this.$notifyError(error)
       }).finally(() => {
diff --git a/src/views/infra/routers/RouterHealthCheck.vue b/src/views/infra/routers/RouterHealthCheck.vue
index 489fe56..31fd45b 100644
--- a/src/views/infra/routers/RouterHealthCheck.vue
+++ b/src/views/infra/routers/RouterHealthCheck.vue
@@ -22,7 +22,7 @@
       banner
       :message="$t('message.action.router.health.checks.disabled.warning')" />
     <div v-else>
-      <a-button :disabled="!('getRouterHealthCheckResults' in $store.getters.apis)" type="primary" icon="download" style="width: 100%; margin-bottom: 15px" @click="showGetHelathCheck">
+      <a-button :disabled="!('getRouterHealthCheckResults' in $store.getters.apis)" type="primary" icon="play-circle" style="width: 100%; margin-bottom: 15px" @click="showGetHelathCheck">
         {{ $t('label.action.router.health.checks') }}
       </a-button>
       <a-table
diff --git a/src/views/network/IngressEgressRuleConfigure.vue b/src/views/network/IngressEgressRuleConfigure.vue
index 1a34351..0d3c4c7 100644
--- a/src/views/network/IngressEgressRuleConfigure.vue
+++ b/src/views/network/IngressEgressRuleConfigure.vue
@@ -62,13 +62,16 @@
           <div class="form__label">{{ $t('label.cidr') }}</div>
           <a-input v-model="newRule.cidrlist"></a-input>
         </div>
-        <div class="form__item" v-if="addType === 'account'">
-          <div class="form__label">{{ $t('label.account.and.security.group') }}</div>
-          <div style="display:flex;">
+        <template v-if="addType === 'account'">
+          <div class="form__item">
+            <div class="form__label">{{ $t('label.account') }}</div>
             <a-input v-model="newRule.usersecuritygrouplist.account" style="margin-right: 10px;"></a-input>
+          </div>
+          <div class="form__item">
+            <div class="form__label">{{ $t('label.securitygroup') }}</div>
             <a-input v-model="newRule.usersecuritygrouplist.group"></a-input>
           </div>
-        </div>
+        </template>
         <div class="form__item" style="flex: 0">
           <a-button :disabled="!('authorizeSecurityGroupInress' in $store.getters.apis) && !('authorizeSecurityGroupEgress' in $store.getters.apis)" type="primary" @click="handleAddRule">{{ $t('label.add') }}</a-button>
         </div>
diff --git a/src/views/offering/ImportBackupOffering.vue b/src/views/offering/ImportBackupOffering.vue
index cff2e75..8af621a 100644
--- a/src/views/offering/ImportBackupOffering.vue
+++ b/src/views/offering/ImportBackupOffering.vue
@@ -74,7 +74,9 @@
         </span>
         <a-select
           allowClear
-          v-decorator="['externalid'] "
+          v-decorator="['externalid', {
+            rules: [{ required: true, message: `${this.$t('message.error.select')}` }]
+          }] "
           :loading="externals.loading">
           <a-select-option v-for="opt in externals.opts" :key="opt.id">
             {{ opt.name }}