mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Async job poller and notifications for actions (#32)
Async job poller tech capability implementation, show notifications on success/fail. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
8b9fccdf11
commit
db42cdf830
@ -234,6 +234,7 @@ import DataView from '@/components/widgets/DataView'
|
|||||||
import InstanceView from '@/components/widgets/InstanceView'
|
import InstanceView from '@/components/widgets/InstanceView'
|
||||||
import Status from '@/components/widgets/Status'
|
import Status from '@/components/widgets/Status'
|
||||||
import { mixinDevice } from '@/utils/mixin.js'
|
import { mixinDevice } from '@/utils/mixin.js'
|
||||||
|
import { constants } from 'crypto';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Resource',
|
name: 'Resource',
|
||||||
@ -506,9 +507,21 @@ export default {
|
|||||||
const closeAction = this.closeAction
|
const closeAction = this.closeAction
|
||||||
const showError = this.$notification['error']
|
const showError = this.$notification['error']
|
||||||
api(this.currentAction.api, params).then(json => {
|
api(this.currentAction.api, params).then(json => {
|
||||||
|
for (const obj in json) {
|
||||||
|
if (obj.includes('response')) {
|
||||||
|
for (const res in json[obj]) {
|
||||||
|
if (res === 'jobid') {
|
||||||
|
this.$store.dispatch('AddAsyncJob', { 'title': this.currentAction.label, 'jobid': json[obj][res], 'description': this.resource.name, 'status': 'progress'})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
closeAction()
|
closeAction()
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
closeAction()
|
closeAction()
|
||||||
|
console.log(error)
|
||||||
showError({
|
showError({
|
||||||
message: 'Request Failed',
|
message: 'Request Failed',
|
||||||
description: error.response.headers['x-description']
|
description: error.response.headers['x-description']
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
</a-list-item>
|
</a-list-item>
|
||||||
<a-list-item v-for="(job, index) in jobs" :key="index">
|
<a-list-item v-for="(job, index) in jobs" :key="index">
|
||||||
<a-list-item-meta :title="job.title" :description="job.description">
|
<a-list-item-meta :title="job.title" :description="job.description">
|
||||||
<a-avatar :style="job.style" :icon="job.icon" slot="avatar"/>
|
<a-avatar :style="notificationAvatar[job.status].style" :icon="notificationAvatar[job.status].icon" slot="avatar"/>
|
||||||
</a-list-item-meta>
|
</a-list-item-meta>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
@ -32,26 +32,89 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { api } from '@/api'
|
||||||
|
import store from '@/store'
|
||||||
|
import { constants } from 'crypto';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HeaderNotice',
|
name: 'HeaderNotice',
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
visible: false,
|
visible: false,
|
||||||
jobs: []
|
jobs: [],
|
||||||
|
poller: null,
|
||||||
|
notificationAvatar: {
|
||||||
|
'done': { 'icon': 'check-circle', 'style': 'backgroundColor:#87d068' },
|
||||||
|
'progress': { 'icon': 'loading', 'style': 'backgroundColor:#ffbf00' },
|
||||||
|
'failed': { 'icon': 'close-circle', 'style': 'backgroundColor:#f56a00' }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showNotifications () {
|
showNotifications () {
|
||||||
this.visible = !this.visible
|
this.visible = !this.visible
|
||||||
this.jobs.push({ 'title': 'Start VM', description: 'VM Deployment', icon: 'check-circle', status: 'done', style: 'backgroundColor:#87d068' })
|
|
||||||
this.jobs.push({ 'title': 'Start VM', description: 'VM Deployment', icon: 'loading', status: 'progress', style: 'backgroundColor:#ffbf00' })
|
|
||||||
this.jobs.push({ 'title': 'Start VM', description: 'VM Deployment', icon: 'close-circle', status: 'failed', style: 'backgroundColor:#f56a00' })
|
|
||||||
},
|
},
|
||||||
clearJobs () {
|
clearJobs () {
|
||||||
this.visible = false
|
this.visible = false
|
||||||
this.jobs = []
|
this.jobs = []
|
||||||
|
this.$store.commit('SET_ASYNC_JOB_IDS', [])
|
||||||
|
},
|
||||||
|
startPolling() {
|
||||||
|
this.poller = setInterval(() => {
|
||||||
|
this.pollJobs()
|
||||||
|
}, 2500)
|
||||||
|
},
|
||||||
|
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
|
||||||
|
this.$notification['success']({
|
||||||
|
message: this.jobs[i].title,
|
||||||
|
description: this.jobs[i].description
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
this.$notification['error']({
|
||||||
|
message: this.jobs[i].title,
|
||||||
|
description: this.jobs[i].description
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch(function (e) {
|
||||||
|
console.log('Error encountered while 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.$store.watch(
|
||||||
|
(state, getters) => getters.asyncJobIds,
|
||||||
|
(newValue, oldValue) => {
|
||||||
|
if (oldValue !== newValue && newValue !== undefined) {
|
||||||
|
this.jobs = newValue.reverse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
4
ui/src/core/bootstrap.js
vendored
4
ui/src/core/bootstrap.js
vendored
@ -12,7 +12,8 @@ import {
|
|||||||
DEFAULT_FIXED_HEADER_HIDDEN,
|
DEFAULT_FIXED_HEADER_HIDDEN,
|
||||||
DEFAULT_FIXED_SIDEMENU,
|
DEFAULT_FIXED_SIDEMENU,
|
||||||
DEFAULT_CONTENT_WIDTH_TYPE,
|
DEFAULT_CONTENT_WIDTH_TYPE,
|
||||||
DEFAULT_MULTI_TAB
|
DEFAULT_MULTI_TAB,
|
||||||
|
ASYNC_JOB_IDS
|
||||||
} from '@/store/mutation-types'
|
} from '@/store/mutation-types'
|
||||||
import config from '@/config/settings'
|
import config from '@/config/settings'
|
||||||
|
|
||||||
@ -29,4 +30,5 @@ export default function Initializer () {
|
|||||||
store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab))
|
store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab))
|
||||||
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
|
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
|
||||||
store.commit('SET_PROJECT', Vue.ls.get(CURRENT_PROJECT))
|
store.commit('SET_PROJECT', Vue.ls.get(CURRENT_PROJECT))
|
||||||
|
store.commit('SET_ASYNC_JOB_IDS', Vue.ls.get(ASYNC_JOB_IDS))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,8 @@ const getters = {
|
|||||||
apis: state => state.user.apis,
|
apis: state => state.user.apis,
|
||||||
userInfo: state => state.user.info,
|
userInfo: state => state.user.info,
|
||||||
addRouters: state => state.permission.addRouters,
|
addRouters: state => state.permission.addRouters,
|
||||||
multiTab: state => state.app.multiTab
|
multiTab: state => state.app.multiTab,
|
||||||
|
asyncJobIds: state => state.user.asyncJobIds
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getters
|
export default getters
|
||||||
|
|||||||
@ -39,7 +39,6 @@ const app = {
|
|||||||
state.device = device
|
state.device = device
|
||||||
},
|
},
|
||||||
TOGGLE_THEME: (state, theme) => {
|
TOGGLE_THEME: (state, theme) => {
|
||||||
// setStore('_DEFAULT_THEME', theme)
|
|
||||||
Vue.ls.set(DEFAULT_THEME, theme)
|
Vue.ls.set(DEFAULT_THEME, theme)
|
||||||
state.theme = theme
|
state.theme = theme
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import md5 from 'md5'
|
import md5 from 'md5'
|
||||||
import { login, logout, api } from '@/api'
|
import { login, logout, api } from '@/api'
|
||||||
import { ACCESS_TOKEN, CURRENT_PROJECT } from '@/store/mutation-types'
|
import { ACCESS_TOKEN, CURRENT_PROJECT, ASYNC_JOB_IDS } from '@/store/mutation-types'
|
||||||
import { welcome } from '@/utils/util'
|
import { welcome } from '@/utils/util'
|
||||||
// import VueCookies from 'vue-cookies'
|
// import VueCookies from 'vue-cookies'
|
||||||
|
|
||||||
@ -13,7 +13,8 @@ const user = {
|
|||||||
avatar: '',
|
avatar: '',
|
||||||
info: {},
|
info: {},
|
||||||
apis: {},
|
apis: {},
|
||||||
project: {}
|
project: {},
|
||||||
|
asyncJobIds: []
|
||||||
},
|
},
|
||||||
|
|
||||||
mutations: {
|
mutations: {
|
||||||
@ -36,6 +37,10 @@ const user = {
|
|||||||
},
|
},
|
||||||
SET_APIS: (state, apis) => {
|
SET_APIS: (state, apis) => {
|
||||||
state.apis = apis
|
state.apis = apis
|
||||||
|
},
|
||||||
|
SET_ASYNC_JOB_IDS: (state, jobsJsonArray) => {
|
||||||
|
Vue.ls.set(ASYNC_JOB_IDS, jobsJsonArray)
|
||||||
|
state.asyncJobIds = jobsJsonArray
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -61,6 +66,7 @@ const user = {
|
|||||||
Vue.ls.set(ACCESS_TOKEN, result.sessionkey, 60 * 60 * 1000)
|
Vue.ls.set(ACCESS_TOKEN, result.sessionkey, 60 * 60 * 1000)
|
||||||
commit('SET_TOKEN', result.sessionkey)
|
commit('SET_TOKEN', result.sessionkey)
|
||||||
commit('SET_PROJECT', {})
|
commit('SET_PROJECT', {})
|
||||||
|
commit('SET_ASYNC_JOB_IDS', [])
|
||||||
|
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@ -104,7 +110,6 @@ const user = {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
Logout ({ commit, state }) {
|
Logout ({ commit, state }) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Remove cookies
|
// Remove cookies
|
||||||
@ -122,6 +127,7 @@ const user = {
|
|||||||
commit('SET_APIS', {})
|
commit('SET_APIS', {})
|
||||||
Vue.ls.remove(CURRENT_PROJECT)
|
Vue.ls.remove(CURRENT_PROJECT)
|
||||||
Vue.ls.remove(ACCESS_TOKEN)
|
Vue.ls.remove(ACCESS_TOKEN)
|
||||||
|
Vue.ls.remove(ASYNC_JOB_IDS)
|
||||||
|
|
||||||
logout(state.token).then(() => {
|
logout(state.token).then(() => {
|
||||||
resolve()
|
resolve()
|
||||||
@ -129,8 +135,12 @@ const user = {
|
|||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
AddAsyncJob ({ commit }, jobJson) {
|
||||||
|
var jobsArray = Vue.ls.get(ASYNC_JOB_IDS, [])
|
||||||
|
jobsArray.push(jobJson)
|
||||||
|
commit('SET_ASYNC_JOB_IDS', jobsArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU'
|
|||||||
export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
|
export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
|
||||||
export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
|
export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
|
||||||
export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB'
|
export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB'
|
||||||
|
export const ASYNC_JOB_IDS = 'ASYNC_JOB_IDS'
|
||||||
|
|
||||||
export const CONTENT_WIDTH_TYPE = {
|
export const CONTENT_WIDTH_TYPE = {
|
||||||
Fluid: 'Fluid',
|
Fluid: 'Fluid',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user