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 bbd4fdd storage: Allow creating snapshot from VM snapshot (#587) bbd4fdd is described below commit bbd4fddefdba3726f16b79b070749a90cad8a71b Author: Rakesh <rakeshv....@gmail.com> AuthorDate: Thu Aug 20 10:44:24 2020 +0200 storage: Allow creating snapshot from VM snapshot (#587) In old UI we support creating snapshot from VM snapshot by selecting one of the volume belonging to VM. Allow the same in new UI Co-authored-by: Rakesh Venkatesh <r.venkat...@global.leaseweb.com> --- src/config/section/storage.js | 13 +- src/locales/en.json | 5 + src/views/storage/CreateSnapshotFromVMSnapshot.vue | 151 +++++++++++++++++++++ 3 files changed, 168 insertions(+), 1 deletion(-) diff --git a/src/config/section/storage.js b/src/config/section/storage.js index a18401b..2e7c205 100644 --- a/src/config/section/storage.js +++ b/src/config/section/storage.js @@ -256,6 +256,7 @@ export default { columns: () => { var fields = ['name', 'state', 'volumename', 'intervaltype', 'created'] if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) { + fields.push('domain') fields.push('account') } return fields @@ -315,7 +316,7 @@ export default { permission: ['listVMSnapshot'], resourceType: 'VMSnapshot', columns: () => { - var fields = ['displayname', 'state', 'name', 'type', 'current', 'parentName', 'created'] + const fields = ['displayname', 'state', 'name', 'type', 'current', 'parentName', 'created'] if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) { fields.push('domain') fields.push('account') @@ -326,6 +327,16 @@ export default { searchFilters: ['name', 'domainid', 'account', 'tags'], actions: [ { + api: 'createSnapshotFromVMSnapshot', + icon: 'camera', + label: 'label.action.create.snapshot.from.vmsnapshot', + message: 'message.action.create.snapshot.from.vmsnapshot', + dataView: true, + popup: true, + show: (record) => { return (record.state === 'Ready' && record.hypervisor === 'KVM') }, + component: () => import('@/views/storage/CreateSnapshotFromVMSnapshot.vue') + }, + { api: 'revertToVMSnapshot', icon: 'sync', label: 'label.action.vmsnapshot.revert', diff --git a/src/locales/en.json b/src/locales/en.json index 63cbba7..2566cab 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -83,6 +83,7 @@ "label.action.create.template.from.vm": "Create Template from VM", "label.action.create.template.from.volume": "Create Template from Volume", "label.action.create.template.processing": "Creating Template....", +"label.action.create.snapshot.from.vmsnapshot": "Create Snapshot from VM Snapshot", "label.action.create.vm": "Create VM", "label.action.create.vm.processing": "Creating VM....", "label.action.create.volume": "Create Volume", @@ -2341,6 +2342,7 @@ "message.action.cancel.maintenance.mode": "Please confirm that you want to cancel this maintenance.", "message.action.change.service.warning.for.instance": "Your instance must be stopped before attempting to change its current service offering.", "message.action.change.service.warning.for.router": "Your router must be stopped before attempting to change its current service offering.", +"message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create snapshot from VM Snapshot", "message.action.delete.backup.offering": "Please confirm that you want to delete this backup offering?", "message.action.delete.cluster": "Please confirm that you want to delete this cluster.", "message.action.delete.disk.offering": "Please confirm that you want to delete this disk offering.", @@ -2595,6 +2597,8 @@ "message.create.internallb.failed": "Failed to create Internal LB", "message.create.internallb.processing": "Creation of Internal LB is in progress", "message.create.service.offering": "Service offering created", +"message.create.snapshot.from.vmsnapshot.failed": "Failed to create Snapshot from VM Snapshot", +"message.create.snapshot.from.vmsnapshot.progress": "Snapshot Creation in progress", "message.create.template": "Are you sure you want to create template?", "message.create.template.vm": "Create VM from template <b id=\"p_name\"></b>", "message.create.template.volume": "Please specify the following information before creating a template of your disk volume: <b><span id=\"volume_name\"></span></b>. Creation of the template can range from several minutes to longer depending on the size of the volume.", @@ -3064,6 +3068,7 @@ "message.success.create.keypair": "Successfully created SSH key pair", "message.success.create.kubernetes.cluter": "Successfully created Kubernetes cluster", "message.success.create.l2.network": "Successfully created L2 network", +"message.success.create.snapshot.from.vmsnapshot": "Successfully created Snapshot from VM snapshot", "message.success.create.volume": "Successfully created volume", "message.success.delete": "Delete success", "message.success.delete.acl.rule": "Successfully removed ACL rule", diff --git a/src/views/storage/CreateSnapshotFromVMSnapshot.vue b/src/views/storage/CreateSnapshotFromVMSnapshot.vue new file mode 100644 index 0000000..6e3be3c --- /dev/null +++ b/src/views/storage/CreateSnapshotFromVMSnapshot.vue @@ -0,0 +1,151 @@ +// 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> + <a-spin :spinning="loading"> + <a-form class="form" :form="form" @submit="handleSubmit" layout="vertical"> + <a-form-item :label="$t('label.name')"> + <a-input + v-decorator="['name', { + rules: [{ required: true, message: $t('message.error.name') }] + }]" + :placeholder="$t('label.snapshot.name')"/> + </a-form-item> + <a-form-item :label="$t('label.volume')"> + <a-select + v-decorator="['volumeid', { + initialValue: selectedVolumeId, + rules: [{ required: true, message: $t('message.error.select') }]}]" + :loading="loading" + @change="id => (volumes.filter(x => x.id === id))" + > + <a-select-option + v-for="(volume, index) in volumes" + :value="volume.id" + :key="index"> + {{ volume.displaytext || volume.name }} + </a-select-option> + </a-select> + </a-form-item> + <div :span="24" class="action-button"> + <a-button @click="closeModal">{{ $t('label.cancel') }}</a-button> + <a-button type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button> + </div> + </a-form> + </a-spin> +</template> + +<script> +import { api } from '@/api' + +export default { + name: 'CreateSnapshotFromVMSnapshot', + props: { + resource: { + type: Object, + required: true + } + }, + data () { + return { + volumes: [], + selectedVolumeId: '', + loading: false + } + }, + beforeCreate () { + this.form = this.$form.createForm(this) + }, + mounted () { + this.fetchData() + }, + methods: { + fetchData () { + this.loading = true + api('listVolumes', { + virtualmachineid: this.resource.virtualmachineid, + listall: true + }).then(json => { + this.volumes = json.listvolumesresponse.volume || [] + this.selectedVolumeId = this.volumes[0].id || '' + }).finally(() => { + this.loading = false + }) + }, + handleSubmit (e) { + this.form.validateFields((err, values) => { + if (err) { + return + } + this.loading = true + api('createSnapshotFromVMSnapshot', { + name: values.name, + volumeid: values.volumeid, + vmsnapshotid: this.resource.id + }).then(response => { + this.$pollJob({ + jobId: response.createsnapshotfromvmsnapshotresponse.jobid, + successMessage: this.$t('message.success.create.snapshot.from.vmsnapshot'), + successMethod: () => { + this.$store.dispatch('AddAsyncJob', { + title: this.$t('message.success.create.snapshot.from.vmsnapshot'), + jobid: response.createsnapshotfromvmsnapshotresponse.jobid, + description: values.name, + status: 'progress' + }) + this.$emit('refresh-data') + }, + errorMessage: this.$t('message.create.snapshot.from.vmsnapshot.failed'), + errorMethod: () => { + this.$emit('refresh-data') + }, + loadingMessage: this.$t('message.create.snapshot.from.vmsnapshot.progress'), + catchMessage: this.$t('error.fetching.async.job.result') + }) + }).catch(error => { + this.$notifyError(error) + }).finally(() => { + this.loading = false + this.$emit('refresh-data') + this.closeModal() + }) + }) + }, + closeModal () { + this.$emit('close-action') + } + } +} +</script> + +<style lang="scss" scoped> +.form { + width: 80vw; + + @media (min-width: 500px) { + width: 400px; + } +} + +.action-button { + text-align: right; + + button { + margin-right: 5px; + } +} +</style>