You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by da...@apache.org on 2021/07/20 04:51:44 UTC

[cloudstack] 01/01: Merge branch '4.15' into main

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

davidjumani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 27d674df7d62f99a16eab4973e79a8052fed90aa
Merge: 05a978c 535761b
Author: davidjumani <dj...@gmail.com>
AuthorDate: Tue Jul 20 10:18:16 2021 +0530

    Merge branch '4.15' into main

 ui/src/components/header/HeaderNotice.vue          |  78 ++---------
 ui/src/components/view/ActionButton.vue            |   1 +
 ui/src/components/view/DedicateData.vue            |  44 ++-----
 ui/src/components/view/DedicateModal.vue           |  42 ++----
 ui/src/core/bootstrap.js                           |   4 +-
 ui/src/store/getters.js                            |   2 +-
 ui/src/store/modules/user.js                       |  33 +++--
 ui/src/store/mutation-types.js                     |   2 +-
 ui/src/utils/plugins.js                            |  28 +++-
 ui/src/views/AutogenView.vue                       | 143 +++++++++++----------
 ui/src/views/compute/AttachIso.vue                 |  11 +-
 ui/src/views/compute/CreateKubernetesCluster.vue   |  14 +-
 ui/src/views/compute/CreateSnapshotWizard.vue      |   8 +-
 ui/src/views/compute/DeployVM.vue                  |  15 +--
 ui/src/views/compute/DestroyVM.vue                 |  10 +-
 ui/src/views/compute/InstanceTab.vue               |  12 --
 ui/src/views/compute/MigrateWizard.vue             |  12 +-
 ui/src/views/compute/ScaleKubernetesCluster.vue    |  14 +-
 ui/src/views/compute/StartVirtualMachine.vue       |  12 +-
 ui/src/views/compute/UpgradeKubernetesCluster.vue  |  14 +-
 ui/src/views/iam/DomainActionForm.vue              |  47 +++----
 ui/src/views/image/IsoZones.vue                    |  17 +--
 ui/src/views/image/TemplateZones.vue               |  17 +--
 ui/src/views/infra/ClusterAdd.vue                  |   8 +-
 ui/src/views/infra/HostAdd.vue                     |   8 +-
 ui/src/views/infra/MigrateData.vue                 |  41 ++----
 ui/src/views/infra/PodAdd.vue                      |   8 +-
 ui/src/views/infra/network/DedicatedVLANTab.vue    |   7 +-
 ui/src/views/infra/network/EditTrafficLabel.vue    |  11 +-
 .../views/infra/network/IpRangesTabManagement.vue  |  16 +--
 ui/src/views/infra/network/IpRangesTabStorage.vue  |  16 +--
 ui/src/views/infra/network/ServiceProvidersTab.vue |   8 +-
 .../infra/network/providers/AddF5LoadBalancer.vue  |  45 ++-----
 .../network/providers/AddNetscalerLoadBalancer.vue |  45 ++-----
 .../infra/network/providers/AddNiciraNvpDevice.vue |  45 ++-----
 .../network/providers/AddPaloAltoFirewall.vue      |  45 ++-----
 .../infra/network/providers/AddSrxFirewall.vue     |  45 ++-----
 .../infra/network/providers/ProviderListView.vue   |  18 +--
 ui/src/views/network/AclListRulesTab.vue           |  35 ++---
 ui/src/views/network/CreateVpc.vue                 |  11 +-
 ui/src/views/network/CreateVpnCustomerGateway.vue  |  11 +-
 ui/src/views/network/FirewallRules.vue             |   4 -
 .../views/network/IngressEgressRuleConfigure.vue   |  10 --
 ui/src/views/network/InternalLBAssignVmForm.vue    |   7 -
 ui/src/views/network/LoadBalancing.vue             |  18 +--
 ui/src/views/network/PortForwarding.vue            |   6 -
 ui/src/views/network/StaticRoutesTab.vue           |  14 +-
 ui/src/views/network/VpcTab.vue                    |  28 ++--
 ui/src/views/network/VpcTiersTab.vue               |   8 +-
 ui/src/views/network/VpnDetails.vue                |   4 -
 ui/src/views/offering/ImportBackupOffering.vue     |  11 +-
 ui/src/views/project/AccountsTab.vue               |  10 +-
 ui/src/views/project/AddAccountOrUserToProject.vue |   2 -
 ui/src/views/project/InvitationTokenTemplate.vue   |   4 +-
 ui/src/views/project/InvitationsTemplate.vue       |   4 +-
 ui/src/views/storage/AttachVolume.vue              |  12 +-
 .../views/storage/CreateSnapshotFromVMSnapshot.vue |  15 +--
 ui/src/views/storage/CreateVolume.vue              |  15 +--
 ui/src/views/storage/MigrateVolume.vue             |   7 -
 ui/src/views/storage/ResizeVolume.vue              |  11 +-
 ui/src/views/storage/RestoreAttachBackupVolume.vue |  10 +-
 ui/src/views/storage/TakeSnapshot.vue              |  12 +-
 62 files changed, 375 insertions(+), 840 deletions(-)

diff --cc ui/src/components/header/HeaderNotice.vue
index 07d9936,d20f606..fb6b3c5
--- a/ui/src/components/header/HeaderNotice.vue
+++ b/ui/src/components/header/HeaderNotice.vue
@@@ -32,12 -32,9 +32,12 @@@
                <a-button size="small" slot="description" @click="clearJobs">{{ $t('label.clear.list') }}</a-button>
              </a-list-item-meta>
            </a-list-item>
-           <a-list-item v-for="(job, index) in jobs" :key="index">
-             <a-list-item-meta :title="job.title">
-               <a-avatar :style="notificationAvatar[job.status].style" :icon="notificationAvatar[job.status].icon" slot="avatar"/><br/>
-               <span v-if="getResourceName(job.description, 'name') && job.path" slot="description"><router-link :to="{ path: job.path}"> {{ getResourceName(job.description, "name") + ' - ' }}</router-link></span>
-               <span v-if="getResourceName(job.description, 'name') && job.path" slot="description"> {{ getResourceName(job.description, "msg") }}</span>
-               <span v-else slot="description"> {{ job.description }} </span>
+           <a-list-item v-for="(notice, index) in notices" :key="index">
+             <a-list-item-meta :title="notice.title" :description="notice.description">
+               <a-avatar :style="notificationAvatar[notice.status].style" :icon="notificationAvatar[notice.status].icon" slot="avatar"/>
++              <span v-if="getResourceName(notice.description, 'name') && notice.path" slot="description"><router-link :to="{ path: notice.path}"> {{ getResourceName(notice.description, "name") + ' - ' }}</router-link></span>
++              <span v-if="getResourceName(notice.description, 'name') && notice.path" slot="description"> {{ getResourceName(notice.description, "msg") }}</span>
++              <span v-else slot="description"> {{ notice.description }} </span>
              </a-list-item-meta>
            </a-list-item>
          </a-list>
@@@ -75,78 -71,17 +74,27 @@@ export default 
        this.visible = !this.visible
      },
      clearJobs () {
-       this.jobs = this.jobs.filter(x => x.status === 'progress')
-       this.$store.commit('SET_ASYNC_JOB_IDS', this.jobs)
-     },
-     startPolling () {
-       this.poller = setInterval(() => {
-         this.pollJobs()
-       }, 4000)
+       this.notices = this.notices.filter(x => x.status === 'progress')
+       this.$store.commit('SET_HEADER_NOTICES', this.notices)
 +    },
 +    getResourceName (description, data) {
 +      if (description) {
 +        if (data === 'name') {
 +          const name = description.match(/\(([^)]+)\)/)
 +          return name ? name[1] : null
 +        }
 +        const msg = description.substring(description.indexOf(')') + 1)
 +        return msg
 +      }
-     },
-     async pollJobs () {
-       var hasUpdated = false
-       for (var i in this.jobs) {
-         if (this.jobs[i].status === 'progress') {
-           await api('queryAsyncJobResult', { jobid: this.jobs[i].jobid }).then(json => {
-             var result = json.queryasyncjobresultresponse
-             if (result.jobstatus === 1 && this.jobs[i].status !== 'done') {
-               hasUpdated = true
-               const title = this.jobs[i].title
-               const description = this.jobs[i].description
-               this.$message.success({
-                 content: title + (description ? ' - ' + description : ''),
-                 key: this.jobs[i].jobid,
-                 duration: 2
-               })
-               this.jobs[i].status = 'done'
-             } else if (result.jobstatus === 2 && this.jobs[i].status !== 'failed') {
-               hasUpdated = true
-               this.jobs[i].status = 'failed'
-               if (result.jobresult.errortext !== null) {
-                 this.jobs[i].description = '(' + this.jobs[i].description + ') ' + result.jobresult.errortext
-               }
-               if (!this.jobs[i].bulkAction) {
-                 this.$notification.error({
-                   message: this.jobs[i].title,
-                   description: this.jobs[i].description,
-                   key: this.jobs[i].jobid,
-                   duration: 0
-                 })
-               }
-             }
-           }).catch(function (e) {
-             console.log(this.$t('error.fetching.async.job.result') + e)
-           })
-         }
-       }
-       if (hasUpdated) {
-         this.$store.commit('SET_ASYNC_JOB_IDS', this.jobs.reverse())
-       }
      }
    },
-   beforeDestroy () {
-     clearInterval(this.poller)
-   },
-   created () {
-     this.startPolling()
-   },
    mounted () {
-     this.jobs = (store.getters.asyncJobIds || []).reverse()
+     this.notices = (store.getters.headerNotices || []).reverse()
      this.$store.watch(
-       (state, getters) => getters.asyncJobIds,
+       (state, getters) => getters.headerNotices,
        (newValue, oldValue) => {
          if (oldValue !== newValue && newValue !== undefined) {
-           this.jobs = newValue.reverse()
+           this.notices = newValue.reverse()
          }
        }
      )
diff --cc ui/src/store/modules/user.js
index d764ae4,d2a5fd6..2cef4a7
--- a/ui/src/store/modules/user.js
+++ b/ui/src/store/modules/user.js
@@@ -45,7 -45,8 +45,7 @@@ const user = 
      apis: {},
      features: {},
      project: {},
--    asyncJobIds: [],
+     headerNotices: [],
      isLdapEnabled: false,
      cloudian: {},
      zones: {},
diff --cc ui/src/utils/plugins.js
index d452b0d,c7a385c..c44cec9
--- a/ui/src/utils/plugins.js
+++ b/ui/src/utils/plugins.js
@@@ -49,10 -53,16 +54,17 @@@ export const pollJobPlugin = 
          showLoading = true,
          catchMessage = i18n.t('label.error.caught'),
          catchMethod = () => {},
 -        action = null
 +        action = null,
 +        bulkAction = false
        } = options
  
+       store.dispatch('AddHeaderNotice', {
+         key: jobId,
+         title: title,
+         description: description,
+         status: 'progress'
+       })
+ 
        api('queryAsyncJobResult', { jobId }).then(json => {
          const result = json.queryasyncjobresultresponse
          if (result.jobstatus === 1) {
@@@ -68,33 -78,47 +80,44 @@@
              key: jobId,
              duration: 2
            })
-           eventBus.$emit('async-job-complete', action)
+           store.dispatch('AddHeaderNotice', {
+             key: jobId,
+             title: title,
+             description: description,
+             status: 'done',
+             duration: 2
+           })
+           if (!action || !('isFetchData' in action) || (action.isFetchData)) {
 -            eventBus.$emit('async-job-complete')
++            eventBus.$emit('async-job-complete', action)
+           }
            successMethod(result)
          } else if (result.jobstatus === 2) {
 -          message.error({
 -            content: errorMessage,
 -            key: jobId,
 -            duration: 1
 -          })
 -          var errTitle = errorMessage
 +          if (!bulkAction) {
 +            message.error({
 +              content: errorMessage,
 +              key: jobId,
 +              duration: 1
 +            })
 +          }
 +          var title = errorMessage
            if (action && action.label) {
 -            errTitle = i18n.t(action.label)
 +            title = i18n.t(action.label)
            }
            var desc = result.jobresult.errortext
            if (name) {
              desc = `(${name}) ${desc}`
            }
 -          notification.error({
 -            message: errTitle,
 -            description: desc,
 -            key: jobId,
 -            duration: 0
 -          })
 -          store.dispatch('AddHeaderNotice', {
 -            key: jobId,
 -            title: title,
 -            description: desc,
 -            status: 'failed',
 -            duration: 0
 -          })
 +          if (!bulkAction) {
 +            notification.error({
 +              message: title,
 +              description: desc,
 +              key: jobId,
 +              duration: 0
 +            })
 +          }
-           eventBus.$emit('async-job-complete', action)
+           if (!action || !('isFetchData' in action) || (action.isFetchData)) {
 -            eventBus.$emit('async-job-complete')
++            eventBus.$emit('async-job-complete', action)
+           }
            errorMethod(result)
          } else if (result.jobstatus === 0) {
            if (showLoading) {
diff --cc ui/src/views/AutogenView.vue
index f88f7fd,f274ecf..f2d4d6f
--- a/ui/src/views/AutogenView.vue
+++ b/ui/src/views/AutogenView.vue
@@@ -443,8 -401,8 +443,9 @@@ export default 
        actions: [],
        formModel: {},
        confirmDirty: false,
 -      promises: [],
 -      firstIndex: 0
 +      firstIndex: 0,
-       modalWidth: '30vw'
++      modalWidth: '30vw',
++      promises: []
      }
    },
    beforeCreate () {
@@@ -476,60 -425,6 +482,59 @@@
      eventBus.$on('exec-action', (action, isGroupAction) => {
        this.execAction(action, isGroupAction)
      })
 +    eventBus.$on('update-bulk-job-status', (items, action) => {
 +      for (const item of items) {
-         this.$store.getters.asyncJobIds.map(function (j) {
++        this.$store.getters.headerNotices.map(function (j) {
 +          if (j.jobid === item.jobid) {
 +            j.bulkAction = action
 +          }
 +        })
 +      }
 +    })
 +    eventBus.$on('update-job-details', (jobId, resourceId) => {
 +      const fullPath = this.$route.fullPath
 +      const path = this.$route.path
-       var jobs = this.$store.getters.asyncJobIds.map(job => {
++      var jobs = this.$store.getters.headerNotices.map(job => {
 +        if (job.jobid === jobId) {
 +          if (resourceId && !path.includes(resourceId)) {
 +            job.path = path + '/' + resourceId
 +          } else {
 +            job.path = fullPath
 +          }
 +        }
 +        return job
 +      })
- 
-       this.$store.commit('SET_ASYNC_JOB_IDS', jobs)
++      this.$store.commit('SET_HEADER_NOTICES', jobs)
 +    })
 +
 +    eventBus.$on('update-resource-state', (selectedItems, resource, state, jobid) => {
 +      if (selectedItems.length === 0) {
 +        return
 +      }
 +      var tempResource = []
 +      if (selectedItems && resource) {
 +        if (resource.includes(',')) {
 +          resource = resource.split(',')
 +          tempResource = resource
 +        } else {
 +          tempResource.push(resource)
 +        }
 +        for (var r = 0; r < tempResource.length; r++) {
 +          var objIndex = 0
 +          if (this.$route.path.includes('/template') || this.$route.path.includes('/iso')) {
 +            objIndex = selectedItems.findIndex(obj => (obj.zoneid === tempResource[r]))
 +          } else {
 +            objIndex = selectedItems.findIndex(obj => (obj.id === tempResource[r] || obj.username === tempResource[r]))
 +          }
 +          if (state && objIndex !== -1) {
 +            selectedItems[objIndex].status = state
 +          }
 +          if (jobid && objIndex !== -1) {
 +            selectedItems[objIndex].jobid = jobid
 +          }
 +        }
 +      }
 +    })
  
      if (this.device === 'desktop') {
        this.pageSize = 20
@@@ -1008,41 -856,34 +1013,42 @@@
        }).then(function () {
        })
      },
 -    pollActionCompletion (jobId, action, resourceName, showLoading = true) {
 +    pollActionCompletion (jobId, action, resourceName, resource, showLoading = true) {
 +      eventBus.$emit('update-job-details', jobId, resource)
-       this.$pollJob({
-         jobId,
-         name: resourceName,
-         successMethod: result => {
-           this.fetchData()
-           if (this.selectedItems.length > 0) {
-             eventBus.$emit('update-resource-state', this.selectedItems, resource, 'success')
-           }
-           if (action.response) {
-             const description = action.response(result.jobresult)
-             if (description) {
-               this.$notification.info({
-                 message: this.$t(action.label),
-                 description: (<span domPropsInnerHTML={description}></span>),
-                 duration: 0
-               })
+       return new Promise((resolve) => {
+         this.$pollJob({
+           jobId,
+           title: this.$t(action.label),
+           description: resourceName,
+           name: resourceName,
+           successMethod: result => {
++            if (this.selectedItems.length > 0) {
++              eventBus.$emit('update-resource-state', this.selectedItems, resource, 'success')
 +            }
-           }
-           if ('successMethod' in action) {
-             action.successMethod(this, result)
-           }
-         },
-         errorMethod: () => {
-           this.fetchData()
-           if (this.selectedItems.length > 0) {
-             eventBus.$emit('update-resource-state', this.selectedItems, resource, 'failed')
-           }
-         },
-         loadingMessage: `${this.$t(action.label)} - ${resourceName}`,
-         showLoading: showLoading,
-         catchMessage: this.$t('error.fetching.async.job.result'),
-         action,
-         bulkAction: `${this.selectedItems.length > 0}` && this.showGroupActionModal
+             if (action.response) {
+               const description = action.response(result.jobresult)
+               if (description) {
+                 this.$notification.info({
+                   message: this.$t(action.label),
+                   description: (<span domPropsInnerHTML={description}></span>),
+                   duration: 0
+                 })
+               }
+             }
+             resolve(true)
+           },
+           errorMethod: () => {
++            if (this.selectedItems.length > 0) {
++              eventBus.$emit('update-resource-state', this.selectedItems, resource, 'failed')
++            }
+             resolve(true)
+           },
+           loadingMessage: `${this.$t(action.label)} - ${resourceName}`,
+           showLoading: showLoading,
+           catchMessage: this.$t('error.fetching.async.job.result'),
 -          action
++          action,
++          bulkAction: `${this.selectedItems.length > 0}` && this.showGroupActionModal
+         })
        })
      },
      fillEditFormFieldValues () {
@@@ -1062,33 -903,9 +1068,34 @@@
          }
        })
      },
 +    handleCancel () {
 +      eventBus.$emit('update-bulk-job-status', this.selectedItems, false)
 +      this.showGroupActionModal = false
 +      this.selectedItems = []
 +      this.selectedColumns = []
 +      this.selectedRowKeys = []
 +      this.message = {}
 +    },
      handleSubmit (e) {
+       this.promises = []
        if (!this.dataView && this.currentAction.groupAction && this.selectedRowKeys.length > 0) {
 +        if (this.selectedRowKeys.length > 0) {
 +          this.selectedColumns = this.chosenColumns
 +          this.selectedItems = this.selectedItems.map(v => ({ ...v, status: 'InProgress' }))
 +          this.selectedColumns.splice(0, 0, {
 +            dataIndex: 'status',
 +            title: this.$t('label.operation.status'),
 +            scopedSlots: { customRender: 'status' },
 +            filters: [
 +              { text: 'In Progress', value: 'InProgress' },
 +              { text: 'Success', value: 'success' },
 +              { text: 'Failed', value: 'failed' }
 +            ]
 +          })
 +          this.showGroupActionModal = true
 +          this.modalInfo.title = this.currentAction.label
 +          this.modalInfo.docHelp = this.currentAction.docHelp
 +        }
          this.form.validateFields((err, values) => {
            if (!err) {
              this.actionLoading = true
@@@ -1096,11 -913,11 +1103,11 @@@
              this.items.map(x => {
                itemsNameMap[x.id] = x.name || x.displaytext || x.id
              })
 -            const paramsList = this.currentAction.groupMap(this.selectedRowKeys, values)
 +            const paramsList = this.currentAction.groupMap(this.selectedRowKeys, values, this.items)
              for (const params of paramsList) {
 -              var resourceName = itemsNameMap[params.id]
 +              var resourceName = itemsNameMap[params.id || params.vmsnapshotid || params.username || params.name]
                // Using a method for this since it's an async call and don't want wrong prarms to be passed
-               this.callGroupApi(params, resourceName)
+               this.promises.push(this.callGroupApi(params, resourceName))
              }
              this.$message.info({
                content: this.$t(this.currentAction.label),
@@@ -1119,46 -935,28 +1125,43 @@@
        }
      },
      callGroupApi (params, resourceName) {
-       const action = this.currentAction
-       api(action.api, params).then(json => {
-         this.handleResponse(json, resourceName, this.getDataIdentifier(params), action, false)
-       }).catch(error => {
-         if ([401].includes(error.response.status)) {
-           return
-         }
-         if (this.selectedItems.length !== 0) {
-           this.$notifyError(error)
-           eventBus.$emit('update-resource-state', this.selectedItems, this.getDataIdentifier(params), 'failed')
-         }
+       return new Promise((resolve, reject) => {
+         const action = this.currentAction
+         api(action.api, params).then(json => {
 -          resolve(this.handleResponse(json, resourceName, action, false))
++          resolve(this.handleResponse(json, resourceName, this.getDataIdentifier(params), action, false))
+           this.closeAction()
+         }).catch(error => {
+           if ([401].includes(error.response.status)) {
+             return
+           }
 -          this.$notifyError(error)
++          if (this.selectedItems.length !== 0) {
++            this.$notifyError(error)
++            eventBus.$emit('update-resource-state', this.selectedItems, this.getDataIdentifier(params), 'failed')
++          }
+         })
        })
      },
 -    handleResponse (response, resourceName, action, showLoading = true) {
 +    getDataIdentifier (params) {
 +      var dataIdentifier = ''
 +      dataIdentifier = params.id || params.username || params.name || params.vmsnapshotid || params.ids
 +      return dataIdentifier
 +    },
 +    handleResponse (response, resourceName, resource, action, showLoading = true) {
        for (const obj in response) {
          if (obj.includes('response')) {
            if (response[obj].jobid) {
-             const jobid = response[obj].jobid
-             this.$store.dispatch('AddAsyncJob', {
-               title: this.$t(action.label),
-               jobid: jobid,
-               description: resourceName,
-               status: 'progress',
-               bulkAction: this.selectedItems.length > 0 && this.showGroupActionModal
+             return new Promise(resolve => {
+               const jobid = response[obj].jobid
 -              resolve(this.pollActionCompletion(jobid, action, resourceName, showLoading))
++              eventBus.$emit('update-resource-state', this.selectedItems, resource, 'InProgress', jobid)
++              resolve(this.pollActionCompletion(jobid, action, resourceName, resource, showLoading))
              })
-             eventBus.$emit('update-resource-state', this.selectedItems, resource, 'InProgress', jobid)
-             this.pollActionCompletion(jobid, action, resourceName, resource, showLoading)
-             return true
            } else {
 +            if (this.selectedItems.length > 0) {
 +              eventBus.$emit('update-resource-state', this.selectedItems, resource, 'success')
 +              if (resource) {
 +                this.selectedItems.filter(item => item === resource)
 +              }
 +            }
              var message = action.successMessage ? this.$t(action.successMessage) : this.$t(action.label) +
                (resourceName ? ' - ' + resourceName : '')
              var duration = 2
@@@ -1253,14 -1051,16 +1256,16 @@@
            args = [action.api, params]
          }
          api(...args).then(json => {
-           hasJobId = this.handleResponse(json, resourceName, this.getDataIdentifier(params), action)
-           if ((action.icon === 'delete' || ['archiveEvents', 'archiveAlerts', 'unmanageVirtualMachine'].includes(action.api)) && this.dataView) {
-             this.$router.go(-1)
-           } else {
-             if (!hasJobId) {
-               this.fetchData()
 -          this.handleResponse(json, resourceName, action).then(jobId => {
++          this.handleResponse(json, resourceName, this.getDataIdentifier(params), action).then(jobId => {
+             hasJobId = jobId
+             if ((action.icon === 'delete' || ['archiveEvents', 'archiveAlerts', 'unmanageVirtualMachine'].includes(action.api)) && this.dataView) {
+               this.$router.go(-1)
+             } else {
+               if (!hasJobId) {
+                 this.fetchData()
+               }
              }
-           }
+           })
            this.closeAction()
          }).catch(error => {
            if ([401].includes(error.response.status)) {
diff --cc ui/src/views/compute/StartVirtualMachine.vue
index 2a534fa,256e0dd..1c5e95a
--- a/ui/src/views/compute/StartVirtualMachine.vue
+++ b/ui/src/views/compute/StartVirtualMachine.vue
@@@ -234,13 -244,8 +229,10 @@@ export default 
              loadingMessage: `${this.$t('label.action.start.instance')} ${this.resource.name}`,
              catchMessage: this.$t('error.fetching.async.job.result'),
              successMessage: `${this.$t('label.action.start.instance')} ${this.resource.name}`,
-             successMethod: () => {
-               this.parentFetchData()
-             },
              response: (result) => { return result.virtualmachine && result.virtualmachine.password ? `The password of VM <b>${result.virtualmachine.displayname}</b> is <b>${result.virtualmachine.password}</b>` : null }
            })
 +          const resourceId = this.resource.id
 +          eventBus.$emit('update-job-details', jobId, resourceId)
            this.closeAction()
          }).catch(error => {
            this.$notifyError(error)
diff --cc ui/src/views/image/IsoZones.vue
index 984c6c7,4df911d..b06573f
--- a/ui/src/views/image/IsoZones.vue
+++ b/ui/src/views/image/IsoZones.vue
@@@ -334,43 -246,21 +334,38 @@@ export default 
        this.deleteLoading = true
        api('deleteIso', params).then(json => {
          const jobId = json.deleteisoresponse.jobid
-         this.$store.dispatch('AddAsyncJob', {
-           title: this.$t('label.action.delete.iso'),
-           jobid: jobId,
-           description: this.resource.name,
-           status: 'progress',
-           bulkAction: this.selectedItems.length > 0 && this.showGroupActionModal
-         })
 +        eventBus.$emit('update-job-details', jobId, null)
          const singleZone = (this.dataSource.length === 1)
          this.$pollJob({
            jobId,
+           title: this.$t('label.action.delete.iso'),
+           description: this.resource.name,
            successMethod: result => {
              if (singleZone) {
 -              this.$router.go(-1)
 +              if (this.selectedItems.length === 0) {
 +                this.$router.go(-1)
 +              }
              } else {
 +              if (this.selectedItems.length === 0) {
 +                this.fetchData()
 +              }
 +            }
 +            if (this.selectedItems.length > 0) {
 +              eventBus.$emit('update-resource-state', this.selectedItems, record.zoneid, 'success')
 +            }
 +          },
 +          errorMethod: () => {
 +            if (this.selectedItems.length === 0) {
                this.fetchData()
              }
 +            if (this.selectedItems.length > 0) {
 +              eventBus.$emit('update-resource-state', this.selectedItems, record.zoneid, 'failed')
 +            }
            },
 -          errorMethod: () => this.fetchData(),
 +          showLoading: !(this.selectedItems.length > 0 && this.showGroupActionModal),
            loadingMessage: `${this.$t('label.deleting.iso')} ${this.resource.name} ${this.$t('label.in.progress')}`,
 -          catchMessage: this.$t('error.fetching.async.job.result')
 +          catchMessage: this.$t('error.fetching.async.job.result'),
 +          bulkAction: this.selectedItems.length > 0 && this.showGroupActionModal
          })
        }).catch(error => {
          this.$notifyError(error)
@@@ -415,15 -305,10 +410,11 @@@
          this.copyLoading = true
          api('copyIso', params).then(json => {
            const jobId = json.copytemplateresponse.jobid
-           this.$store.dispatch('AddAsyncJob', {
-             title: this.$t('label.action.copy.iso'),
-             jobid: jobId,
-             description: this.resource.name,
-             status: 'progress'
-           })
 +          eventBus.$emit('update-job-details', jobId, null)
            this.$pollJob({
              jobId,
+             title: this.$t('label.action.copy.iso'),
+             description: this.resource.name,
              successMethod: result => {
                this.fetchData()
              },
diff --cc ui/src/views/image/TemplateZones.vue
index d8ed93b,edbedbf..692096c
--- a/ui/src/views/image/TemplateZones.vue
+++ b/ui/src/views/image/TemplateZones.vue
@@@ -382,14 -258,6 +382,7 @@@ export default 
        this.deleteLoading = true
        api('deleteTemplate', params).then(json => {
          const jobId = json.deletetemplateresponse.jobid
-         this.$store.dispatch('AddAsyncJob', {
-           title: this.$t('label.action.delete.template'),
-           jobid: jobId,
-           description: this.resource.name,
-           status: 'progress',
-           bulkAction: this.selectedItems.length > 0 && this.showGroupActionModal
-         })
 +        eventBus.$emit('update-job-details', jobId, null)
          const singleZone = (this.dataSource.length === 1)
          this.$pollJob({
            jobId,
@@@ -483,15 -327,10 +478,11 @@@
          this.copyLoading = true
          api('copyTemplate', params).then(json => {
            const jobId = json.copytemplateresponse.jobid
-           this.$store.dispatch('AddAsyncJob', {
-             title: this.$t('label.action.copy.template'),
-             jobid: jobId,
-             description: this.resource.name,
-             status: 'progress'
-           })
 +          eventBus.$emit('update-job-details', jobId, null)
            this.$pollJob({
              jobId,
+             title: this.$t('label.action.copy.template'),
+             description: this.resource.name,
              successMethod: result => {
                this.fetchData()
              },
diff --cc ui/src/views/network/LoadBalancing.vue
index 8315219,66dd0d8..08e99fa
--- a/ui/src/views/network/LoadBalancing.vue
+++ b/ui/src/views/network/LoadBalancing.vue
@@@ -1032,52 -934,29 +1022,52 @@@ export default 
        api('deleteLoadBalancerRule', {
          id: rule.id
        }).then(response => {
 +        const jobId = response.deleteloadbalancerruleresponse.jobid
 +        this.$store.dispatch('AddAsyncJob', {
 +          title: this.$t('label.action.delete.load.balancer'),
 +          jobid: jobId,
 +          description: rule.id,
 +          status: 'progress',
 +          bulkAction: this.selectedItems.length > 0 && this.showGroupActionModal
 +        })
 +        eventBus.$emit('update-job-details', jobId, null)
          this.$pollJob({
 -          jobId: response.deleteloadbalancerruleresponse.jobid,
 +          jobId: jobId,
            successMessage: this.$t('message.success.remove.rule'),
            successMethod: () => {
 -            this.parentToggleLoading()
 -            this.fetchData()
 +            if (this.selectedItems.length > 0) {
 +              eventBus.$emit('update-resource-state', this.selectedItems, rule.id, 'success')
 +            }
 +            if (this.selectedRowKeys.length === 0) {
-               this.parentFetchData()
 +              this.parentToggleLoading()
++              this.fetchData()
 +            }
              this.closeModal()
            },
            errorMessage: this.$t('message.remove.rule.failed'),
            errorMethod: () => {
 -            this.parentToggleLoading()
 -            this.fetchData()
 +            if (this.selectedItems.length > 0) {
 +              eventBus.$emit('update-resource-state', this.selectedItems, rule.id, 'failed')
 +            }
 +            if (this.selectedRowKeys.length === 0) {
-               this.parentFetchData()
 +              this.parentToggleLoading()
++              this.fetchData()
 +            }
              this.closeModal()
            },
            loadingMessage: this.$t('message.delete.rule.processing'),
            catchMessage: this.$t('error.fetching.async.job.result'),
            catchMethod: () => {
 -            this.parentToggleLoading()
 -            this.fetchData()
 +            if (this.selectedRowKeys.length === 0) {
-               this.parentFetchData()
 +              this.parentToggleLoading()
++              this.parentFetchData()
 +            }
              this.closeModal()
 -          }
 +          },
 +          bulkAction: `${this.selectedItems.length > 0}` && this.showGroupActionModal
          })
        }).catch(error => {
 +        console.log(error)
          this.$notifyError(error)
          this.loading = false
        })