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 b384cd4 compute: VM assign to other account/project/domain (#69) b384cd4 is described below commit b384cd4b5a298d5b958f0bb2631ff0d2d0dd6901 Author: Ritchie Vincent <rfcvinc...@gmail.com> AuthorDate: Sat Dec 14 15:28:56 2019 +0000 compute: VM assign to other account/project/domain (#69) Custom form to assign a VM to either an account or a project. Signed-off-by: Rohit Yadav <rohit.ya...@shapeblue.com> --- src/config/section/compute.js | 2 + src/locales/en.json | 3 + src/utils/request.js | 4 +- src/views/AutogenView.vue | 9 +- src/views/compute/AssignInstance.vue | 274 +++++++++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 4 deletions(-) diff --git a/src/config/section/compute.js b/src/config/section/compute.js index a4241a2..51a7ffc 100644 --- a/src/config/section/compute.js +++ b/src/config/section/compute.js @@ -241,6 +241,8 @@ export default { icon: 'user-add', label: 'Assign Instance to Another Account', dataView: true, + component: () => import('@/views/compute/AssignInstance'), + popup: true, show: (record) => { return ['Stopped'].includes(record.state) }, args: ['virtualmachineid', 'account', 'domainid'], mapping: { diff --git a/src/locales/en.json b/src/locales/en.json index ec0282e..d330ce2 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -631,6 +631,7 @@ "memoryusedgb": "Used", "memused": "Memory Usage", "message.edit.account": "Edit (\"-1\" indicates no limit to the amount of resources create)", +"message.assign.instance.another": "Please specify the account type, domain, account name and network (optional) of the new account. <br> If the default nic of the vm is on a shared network, CloudStack will check if the network can be used by the new account if you do not specify one network. <br> If the default nic of the vm is on a isolated network, and the new account has more one isolated networks, you should specify one.", "minCPUNumber": "Min CPU Cores", "minInstance": "Min Instances", "minIops": "Min IOPS", @@ -754,6 +755,7 @@ "redundantvpcrouter": "Redundant VPC", "reenterpassword": "Re-enter Password", "relationaloperator": "Operator", +"required": "Required", "requireshvm": "HVM", "requiresupgrade": "Requires Upgrade", "reservedSystemEndIp": "End Reserved system IP", @@ -836,6 +838,7 @@ "storagepolicy": "Storage policy", "storagetype": "Storage Type", "subdomainaccess": "Subdomain Access", +"submit": "Submit", "supportedServices": "Supported Services", "supportspublicaccess": "Supports Public Access", "supportsregionLevelvpc": "Supports Region Level VPC", diff --git a/src/utils/request.js b/src/utils/request.js index c38e7f4..235793a 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -57,10 +57,10 @@ const err = (error) => { // request interceptor service.interceptors.request.use(config => { - const project = Vue.ls.get(CURRENT_PROJECT) if (config && config.params) { config.params.response = 'json' - if (project && project.id) { + const project = Vue.ls.get(CURRENT_PROJECT) + if (!config.params.projectid && project && project.id) { config.params.projectid = project.id } } diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue index 0050239..1e57073 100644 --- a/src/views/AutogenView.vue +++ b/src/views/AutogenView.vue @@ -19,10 +19,10 @@ <div> <a-card class="breadcrumb-card"> <a-row> - <a-col :span="14"> + <a-col :span="12"> <breadcrumb style="padding-top: 6px" /> </a-col> - <a-col :span="10"> + <a-col :span="12"> <span style="float: right"> <a-tooltip placement="bottom"> <template slot="title"> @@ -263,6 +263,11 @@ export default { Status }, mixins: [mixinDevice], + provide: function () { + return { + parentFetchData: this.fetchData + } + }, data () { return { apiName: '', diff --git a/src/views/compute/AssignInstance.vue b/src/views/compute/AssignInstance.vue new file mode 100644 index 0000000..3945e90 --- /dev/null +++ b/src/views/compute/AssignInstance.vue @@ -0,0 +1,274 @@ +// 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> + <p v-html="$t('message.assign.instance.another')"></p> + + <div class="form"> + + <div v-if="loading" class="loading"> + <a-icon type="loading" style="color: #1890ff;"></a-icon> + </div> + + <div class="form__item"> + <p class="form__label">{{ $t('accounttype') }}</p> + <a-select v-model="selectedAccountType" defaultValue="account"> + <a-select-option :value="$t('account')">{{ $t('account') }}</a-select-option> + <a-select-option :value="$t('project')">{{ $t('project') }}</a-select-option> + </a-select> + </div> + + <div class="form__item"> + <p class="form__label"><span class="required">*</span>{{ $t('domain') }}</p> + <a-select @change="changeDomain" v-model="selectedDomain" :defaultValue="selectedDomain"> + <a-select-option v-for="domain in domains" :key="domain.name" :value="domain.id"> + {{ domain.path }} + </a-select-option> + </a-select> + </div> + + <template v-if="selectedAccountType === 'Account'"> + <div class="form__item"> + <p class="form__label"><span class="required">*</span>{{ $t('account') }}</p> + <a-select @change="changeAccount" v-model="selectedAccount"> + <a-select-option v-for="account in accounts" :key="account.name" :value="account.name"> + {{ account.name }} + </a-select-option> + </a-select> + <span v-if="accountError" class="required">{{ $t('required') }}</span> + </div> + </template> + + <template v-else> + <div class="form__item"> + <p class="form__label"><span class="required">*</span>{{ $t('project') }}</p> + <a-select @change="changeProject" v-model="selectedProject"> + <a-select-option v-for="project in projects" :key="project.id" :value="project.id"> + {{ project.name }} + </a-select-option> + </a-select> + <span v-if="projectError" class="required">{{ $t('required') }}</span> + </div> + </template> + + <div class="form__item"> + <p class="form__label">{{ $t('network') }}</p> + <a-select v-model="selectedNetwork"> + <a-select-option v-for="network in networks" :key="network.id" :value="network.id"> + {{ network.name ? network.name : '-' }} + </a-select-option> + </a-select> + </div> + + <a-button type="primary" class="submit-btn" @click="submitData"> + {{ $t('submit') }} + </a-button> + + </div> + + </div> +</template> + +<script> +import { api } from '@/api' + +export default { + name: 'AssignInstance', + props: { + resource: { + type: Object, + required: true + } + }, + inject: ['parentFetchData'], + data () { + return { + domains: [], + accounts: [], + projects: [], + networks: [], + selectedAccountType: 'Account', + selectedDomain: null, + selectedAccount: null, + selectedProject: null, + selectedNetwork: null, + accountError: false, + projectError: false, + loading: false + } + }, + mounted () { + this.fetchData() + }, + methods: { + fetchData () { + this.loading = true + api('listDomains', { + response: 'json', + listAll: true, + details: 'min' + }).then(response => { + this.domains = response.listdomainsresponse.domain + this.selectedDomain = this.domains[0].id + this.fetchAccounts() + this.fetchProjects() + }) + }, + fetchAccounts () { + this.loading = true + api('listAccounts', { + response: 'json', + domainId: this.selectedDomain, + state: 'Enabled', + listAll: true + }).then(response => { + this.accounts = response.listaccountsresponse.account + this.loading = false + }) + }, + fetchProjects () { + this.loading = true + api('listProjects', { + response: 'json', + domainId: this.selectedDomain, + state: 'Active', + details: 'min', + listAll: true + }).then(response => { + this.projects = response.listprojectsresponse.project + this.loading = false + }) + }, + fetchNetworks () { + this.loading = true + api('listNetworks', { + response: 'json', + domainId: this.selectedDomain, + listAll: true, + isrecursive: false, + account: this.selectedAccount, + projectid: this.selectedProject + }).then(response => { + this.networks = response.listnetworksresponse.network + this.loading = false + }) + }, + changeDomain () { + this.selectedAccount = null + this.fetchAccounts() + this.fetchProjects() + }, + changeAccount () { + this.selectedProject = null + this.fetchNetworks() + }, + changeProject () { + this.selectedAccount = null + this.fetchNetworks() + }, + submitData () { + let variableKey = '' + let variableValue = '' + + if (this.selectedAccountType === 'Account') { + if (!this.selectedAccount) { + this.accountError = true + return + } + variableKey = 'account' + variableValue = this.selectedAccount + } else if (this.selectedAccountType === 'Project') { + if (!this.selectedProject) { + this.projectError = true + return + } + variableKey = 'projectid' + variableValue = this.selectedProject + } + + this.loading = true + api('assignVirtualMachine', { + response: 'json', + virtualmachineid: this.resource.id, + domainid: this.selectedDomain, + [variableKey]: variableValue, + networkids: this.selectedNetwork + }).then(response => { + this.$notification.success({ + message: 'Successfully assigned instance' + }) + this.loading = false + this.$parent.$parent.close() + this.parentFetchData() + }).catch(error => { + this.$notification.error({ + message: 'Failed to assign instance', + description: error.response.data.assignvirtualmachineresponse.errortext && error.response.data.assignvirtualmachineresponse.errortext + }) + this.$parent.$parent.close() + this.parentFetchData() + }) + } + } +} +</script> + +<style scoped lang="scss"> + .form { + display: flex; + flex-direction: column; + + &__item { + display: flex; + flex-direction: column; + width: 100%; + margin-bottom: 10px; + } + + &__label { + display: flex; + font-weight: bold; + margin-bottom: 5px; + } + + } + + .submit-btn { + margin-top: 10px; + align-self: flex-end; + } + + .required { + margin-right: 2px; + color: red; + font-size: 0.7rem; + } + + .loading { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 3rem; + } +</style>