mirror of https://github.com/apache/cloudstack.git
storage: Volume storage migration action (#72)
Adds a custom volume storage migration form This fixes #70 Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
185e6043e1
commit
200f89bc08
|
|
@ -140,7 +140,12 @@ export default {
|
|||
icon: 'cloud-download',
|
||||
label: 'Download ISO',
|
||||
dataView: true,
|
||||
args: ['zoneid', 'mode']
|
||||
args: ['zoneid', 'mode'],
|
||||
mapping: {
|
||||
mode: {
|
||||
value: (record) => { return 'HTTP_DOWNLOAD' }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'updateIsoPermissions',
|
||||
|
|
|
|||
|
|
@ -116,7 +116,9 @@ export default {
|
|||
label: 'Migrate Volume',
|
||||
args: ['volumeid', 'storageid', 'livemigrate'],
|
||||
dataView: true,
|
||||
show: (record) => { return 'virtualmachineid' in record && record.virtualmachineid },
|
||||
show: (record) => { return record && record.state === 'Ready' },
|
||||
popup: true,
|
||||
component: () => import('@/views/storage/MigrateVolume.vue'),
|
||||
mapping: {
|
||||
volumeid: {
|
||||
value: (record) => { return record.id }
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ import './core/use'
|
|||
import './core/ext'
|
||||
import './permission' // permission control
|
||||
import './utils/filter' // global filter
|
||||
import { pollJobPlugin } from './utils/plugins'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(VueAxios, router)
|
||||
Vue.use(pollJobPlugin)
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import { api } from '@/api'
|
||||
import { message, notification } from 'ant-design-vue'
|
||||
|
||||
export const pollJobPlugin = {
|
||||
|
||||
install (Vue) {
|
||||
Vue.prototype.$pollJob = function (options) {
|
||||
/**
|
||||
* @param {String} jobId
|
||||
* @param {String} [successMessage=Success]
|
||||
* @param {Function} [successMethod=() => {}]
|
||||
* @param {String} [errorMessage=Error]
|
||||
* @param {Function} [errorMethod=() => {}]
|
||||
* @param {String} [loadingMessage=Loading...]
|
||||
* @param {String} [catchMessage=Error caught]
|
||||
* @param {Function} [catchMethod=() => {}]
|
||||
* @param {Number} [loadingDuration=3]
|
||||
*/
|
||||
const {
|
||||
jobId,
|
||||
successMessage = 'Success',
|
||||
successMethod = () => {},
|
||||
errorMessage = 'Error',
|
||||
errorMethod = () => {},
|
||||
loadingMessage = 'Loading...',
|
||||
catchMessage = 'Error caught',
|
||||
catchMethod = () => {},
|
||||
loadingDuration = 3
|
||||
} = options
|
||||
|
||||
api('queryAsyncJobResult', { jobId }).then(json => {
|
||||
const result = json.queryasyncjobresultresponse
|
||||
|
||||
if (result.jobstatus === 1) {
|
||||
message.success(successMessage)
|
||||
successMethod()
|
||||
} else if (result.jobstatus === 2) {
|
||||
notification.error({
|
||||
message: errorMessage,
|
||||
description: result.jobresult.errortext
|
||||
})
|
||||
errorMethod()
|
||||
} else if (result.jobstatus === 0) {
|
||||
message
|
||||
.loading(loadingMessage, loadingDuration)
|
||||
.then(() => this.$pollJob(options))
|
||||
}
|
||||
}).catch(e => {
|
||||
console.error(`${catchMessage} - ${e}`)
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: catchMessage
|
||||
})
|
||||
catchMethod && catchMethod()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -265,7 +265,8 @@ export default {
|
|||
mixins: [mixinDevice],
|
||||
provide: function () {
|
||||
return {
|
||||
parentFetchData: this.fetchData
|
||||
parentFetchData: this.fetchData,
|
||||
parentToggleLoading: this.toggleLoading
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
|
@ -718,6 +719,9 @@ export default {
|
|||
changeResource (resource) {
|
||||
this.treeSelected = resource
|
||||
this.resource = this.treeSelected
|
||||
},
|
||||
toggleLoading () {
|
||||
this.loading = !this.loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,6 @@ export default {
|
|||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.host-item {
|
||||
|
|
@ -199,7 +198,6 @@ export default {
|
|||
@media (min-width: 760px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__value {
|
||||
|
|
@ -216,9 +214,7 @@ export default {
|
|||
margin-right: 40px;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__title {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
<template>
|
||||
<div class="migrate-volume-container">
|
||||
|
||||
<div class="modal-form">
|
||||
<p class="modal-form__label">{{ $t('storagePool') }}</p>
|
||||
<a-select v-model="selectedStoragePool" style="width: 100%;">
|
||||
<a-select-option v-for="(storagePool, index) in storagePools" :value="storagePool.id" :key="index">
|
||||
{{ storagePool.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<template v-if="this.resource.virtualmachineid">
|
||||
<p class="modal-form__label" @click="replaceDiskOffering = !replaceDiskOffering" style="cursor:pointer;">
|
||||
{{ $t('useNewDiskOffering') }}
|
||||
</p>
|
||||
<a-checkbox v-model="replaceDiskOffering" />
|
||||
|
||||
<template v-if="replaceDiskOffering">
|
||||
<p class="modal-form__label">{{ $t('newDiskOffering') }}</p>
|
||||
<a-select v-model="selectedDiskOffering" style="width: 100%;">
|
||||
<a-select-option v-for="(diskOffering, index) in diskOfferings" :value="diskOffering.id" :key="index">
|
||||
{{ diskOffering.displaytext }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<div class="actions">
|
||||
<a-button @click="closeModal">
|
||||
{{ $t('Cancel') }}
|
||||
</a-button>
|
||||
<a-button type="primary" @click="submitMigrateVolume">
|
||||
{{ $t('OK') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'MigrateVolume',
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
inject: ['parentFetchData', 'parentToggleLoading'],
|
||||
data () {
|
||||
return {
|
||||
storagePools: [],
|
||||
selectedStoragePool: null,
|
||||
diskOfferings: [],
|
||||
replaceDiskOffering: !!this.resource.virtualmachineid,
|
||||
selectedDiskOffering: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchStoragePools()
|
||||
this.resource.virtualmachineid && this.fetchDiskOfferings()
|
||||
},
|
||||
methods: {
|
||||
fetchStoragePools () {
|
||||
api('listStoragePools', {
|
||||
zoneid: this.resource.zoneid
|
||||
}).then(response => {
|
||||
this.storagePools = response.liststoragepoolsresponse.storagepool
|
||||
this.selectedStoragePool = this.storagePools[0].id
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.errorresponse.errortext
|
||||
})
|
||||
this.closeModal()
|
||||
})
|
||||
},
|
||||
fetchDiskOfferings () {
|
||||
api('listDiskOfferings', {
|
||||
listall: true
|
||||
}).then(response => {
|
||||
this.diskOfferings = response.listdiskofferingsresponse.diskoffering
|
||||
this.selectedDiskOffering = this.diskOfferings[0].id
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.errorresponse.errortext
|
||||
})
|
||||
this.closeModal()
|
||||
})
|
||||
},
|
||||
closeModal () {
|
||||
this.$parent.$parent.close()
|
||||
},
|
||||
submitMigrateVolume () {
|
||||
this.closeModal()
|
||||
this.parentToggleLoading()
|
||||
api('migrateVolume', {
|
||||
livemigrate: this.resource.vmstate === 'Running',
|
||||
storageid: this.selectedStoragePool,
|
||||
volumeid: this.resource.id,
|
||||
newdiskofferingid: this.replaceDiskOffering ? this.selectedDiskOffering : null
|
||||
}).then(response => {
|
||||
this.$pollJob({
|
||||
jobId: response.migratevolumeresponse.jobid,
|
||||
successMessage: `Successfully migrated volume`,
|
||||
successMethod: () => {
|
||||
this.parentFetchData()
|
||||
this.parentToggleLoading()
|
||||
},
|
||||
errorMessage: 'Migrating volume failed',
|
||||
errorMethod: () => {
|
||||
this.parentFetchData()
|
||||
this.parentToggleLoading()
|
||||
},
|
||||
loadingMessage: `Migrating volume...`,
|
||||
catchMessage: 'Error encountered while fetching async job result',
|
||||
catchMethod: () => {
|
||||
this.parentFetchData()
|
||||
this.parentToggleLoading()
|
||||
}
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.errorresponse.errortext
|
||||
})
|
||||
this.closeModal()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.migrate-volume-container {
|
||||
width: 95vw;
|
||||
max-width: 100%;
|
||||
|
||||
@media (min-width: 760px) {
|
||||
width: 50vw;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
|
||||
button {
|
||||
&:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-form {
|
||||
margin-top: -20px;
|
||||
|
||||
&__label {
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue