Skip to content

Commit 921ffe8

Browse files
RitchieVincentrohityadavcloud
authored andcommitted
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 <[email protected]>
1 parent b638923 commit 921ffe8

File tree

5 files changed

+288
-4
lines changed

5 files changed

+288
-4
lines changed

ui/src/config/section/compute.js

+2
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ export default {
241241
icon: 'user-add',
242242
label: 'Assign Instance to Another Account',
243243
dataView: true,
244+
component: () => import('@/views/compute/AssignInstance'),
245+
popup: true,
244246
show: (record) => { return ['Stopped'].includes(record.state) },
245247
args: ['virtualmachineid', 'account', 'domainid'],
246248
mapping: {

ui/src/locales/en.json

+3
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@
631631
"memoryusedgb": "Used",
632632
"memused": "Memory Usage",
633633
"message.edit.account": "Edit (\"-1\" indicates no limit to the amount of resources create)",
634+
"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.",
634635
"minCPUNumber": "Min CPU Cores",
635636
"minInstance": "Min Instances",
636637
"minIops": "Min IOPS",
@@ -754,6 +755,7 @@
754755
"redundantvpcrouter": "Redundant VPC",
755756
"reenterpassword": "Re-enter Password",
756757
"relationaloperator": "Operator",
758+
"required": "Required",
757759
"requireshvm": "HVM",
758760
"requiresupgrade": "Requires Upgrade",
759761
"reservedSystemEndIp": "End Reserved system IP",
@@ -836,6 +838,7 @@
836838
"storagepolicy": "Storage policy",
837839
"storagetype": "Storage Type",
838840
"subdomainaccess": "Subdomain Access",
841+
"submit": "Submit",
839842
"supportedServices": "Supported Services",
840843
"supportspublicaccess": "Supports Public Access",
841844
"supportsregionLevelvpc": "Supports Region Level VPC",

ui/src/utils/request.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ const err = (error) => {
5757

5858
// request interceptor
5959
service.interceptors.request.use(config => {
60-
const project = Vue.ls.get(CURRENT_PROJECT)
6160
if (config && config.params) {
6261
config.params.response = 'json'
63-
if (project && project.id) {
62+
const project = Vue.ls.get(CURRENT_PROJECT)
63+
if (!config.params.projectid && project && project.id) {
6464
config.params.projectid = project.id
6565
}
6666
}

ui/src/views/AutogenView.vue

+7-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
<div>
2020
<a-card class="breadcrumb-card">
2121
<a-row>
22-
<a-col :span="14">
22+
<a-col :span="12">
2323
<breadcrumb style="padding-top: 6px" />
2424
</a-col>
25-
<a-col :span="10">
25+
<a-col :span="12">
2626
<span style="float: right">
2727
<a-tooltip placement="bottom">
2828
<template slot="title">
@@ -263,6 +263,11 @@ export default {
263263
Status
264264
},
265265
mixins: [mixinDevice],
266+
provide: function () {
267+
return {
268+
parentFetchData: this.fetchData
269+
}
270+
},
266271
data () {
267272
return {
268273
apiName: '',
+274
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
<template>
19+
<div>
20+
<p v-html="$t('message.assign.instance.another')"></p>
21+
22+
<div class="form">
23+
24+
<div v-if="loading" class="loading">
25+
<a-icon type="loading" style="color: #1890ff;"></a-icon>
26+
</div>
27+
28+
<div class="form__item">
29+
<p class="form__label">{{ $t('accounttype') }}</p>
30+
<a-select v-model="selectedAccountType" defaultValue="account">
31+
<a-select-option :value="$t('account')">{{ $t('account') }}</a-select-option>
32+
<a-select-option :value="$t('project')">{{ $t('project') }}</a-select-option>
33+
</a-select>
34+
</div>
35+
36+
<div class="form__item">
37+
<p class="form__label"><span class="required">*</span>{{ $t('domain') }}</p>
38+
<a-select @change="changeDomain" v-model="selectedDomain" :defaultValue="selectedDomain">
39+
<a-select-option v-for="domain in domains" :key="domain.name" :value="domain.id">
40+
{{ domain.path }}
41+
</a-select-option>
42+
</a-select>
43+
</div>
44+
45+
<template v-if="selectedAccountType === 'Account'">
46+
<div class="form__item">
47+
<p class="form__label"><span class="required">*</span>{{ $t('account') }}</p>
48+
<a-select @change="changeAccount" v-model="selectedAccount">
49+
<a-select-option v-for="account in accounts" :key="account.name" :value="account.name">
50+
{{ account.name }}
51+
</a-select-option>
52+
</a-select>
53+
<span v-if="accountError" class="required">{{ $t('required') }}</span>
54+
</div>
55+
</template>
56+
57+
<template v-else>
58+
<div class="form__item">
59+
<p class="form__label"><span class="required">*</span>{{ $t('project') }}</p>
60+
<a-select @change="changeProject" v-model="selectedProject">
61+
<a-select-option v-for="project in projects" :key="project.id" :value="project.id">
62+
{{ project.name }}
63+
</a-select-option>
64+
</a-select>
65+
<span v-if="projectError" class="required">{{ $t('required') }}</span>
66+
</div>
67+
</template>
68+
69+
<div class="form__item">
70+
<p class="form__label">{{ $t('network') }}</p>
71+
<a-select v-model="selectedNetwork">
72+
<a-select-option v-for="network in networks" :key="network.id" :value="network.id">
73+
{{ network.name ? network.name : '-' }}
74+
</a-select-option>
75+
</a-select>
76+
</div>
77+
78+
<a-button type="primary" class="submit-btn" @click="submitData">
79+
{{ $t('submit') }}
80+
</a-button>
81+
82+
</div>
83+
84+
</div>
85+
</template>
86+
87+
<script>
88+
import { api } from '@/api'
89+
90+
export default {
91+
name: 'AssignInstance',
92+
props: {
93+
resource: {
94+
type: Object,
95+
required: true
96+
}
97+
},
98+
inject: ['parentFetchData'],
99+
data () {
100+
return {
101+
domains: [],
102+
accounts: [],
103+
projects: [],
104+
networks: [],
105+
selectedAccountType: 'Account',
106+
selectedDomain: null,
107+
selectedAccount: null,
108+
selectedProject: null,
109+
selectedNetwork: null,
110+
accountError: false,
111+
projectError: false,
112+
loading: false
113+
}
114+
},
115+
mounted () {
116+
this.fetchData()
117+
},
118+
methods: {
119+
fetchData () {
120+
this.loading = true
121+
api('listDomains', {
122+
response: 'json',
123+
listAll: true,
124+
details: 'min'
125+
}).then(response => {
126+
this.domains = response.listdomainsresponse.domain
127+
this.selectedDomain = this.domains[0].id
128+
this.fetchAccounts()
129+
this.fetchProjects()
130+
})
131+
},
132+
fetchAccounts () {
133+
this.loading = true
134+
api('listAccounts', {
135+
response: 'json',
136+
domainId: this.selectedDomain,
137+
state: 'Enabled',
138+
listAll: true
139+
}).then(response => {
140+
this.accounts = response.listaccountsresponse.account
141+
this.loading = false
142+
})
143+
},
144+
fetchProjects () {
145+
this.loading = true
146+
api('listProjects', {
147+
response: 'json',
148+
domainId: this.selectedDomain,
149+
state: 'Active',
150+
details: 'min',
151+
listAll: true
152+
}).then(response => {
153+
this.projects = response.listprojectsresponse.project
154+
this.loading = false
155+
})
156+
},
157+
fetchNetworks () {
158+
this.loading = true
159+
api('listNetworks', {
160+
response: 'json',
161+
domainId: this.selectedDomain,
162+
listAll: true,
163+
isrecursive: false,
164+
account: this.selectedAccount,
165+
projectid: this.selectedProject
166+
}).then(response => {
167+
this.networks = response.listnetworksresponse.network
168+
this.loading = false
169+
})
170+
},
171+
changeDomain () {
172+
this.selectedAccount = null
173+
this.fetchAccounts()
174+
this.fetchProjects()
175+
},
176+
changeAccount () {
177+
this.selectedProject = null
178+
this.fetchNetworks()
179+
},
180+
changeProject () {
181+
this.selectedAccount = null
182+
this.fetchNetworks()
183+
},
184+
submitData () {
185+
let variableKey = ''
186+
let variableValue = ''
187+
188+
if (this.selectedAccountType === 'Account') {
189+
if (!this.selectedAccount) {
190+
this.accountError = true
191+
return
192+
}
193+
variableKey = 'account'
194+
variableValue = this.selectedAccount
195+
} else if (this.selectedAccountType === 'Project') {
196+
if (!this.selectedProject) {
197+
this.projectError = true
198+
return
199+
}
200+
variableKey = 'projectid'
201+
variableValue = this.selectedProject
202+
}
203+
204+
this.loading = true
205+
api('assignVirtualMachine', {
206+
response: 'json',
207+
virtualmachineid: this.resource.id,
208+
domainid: this.selectedDomain,
209+
[variableKey]: variableValue,
210+
networkids: this.selectedNetwork
211+
}).then(response => {
212+
this.$notification.success({
213+
message: 'Successfully assigned instance'
214+
})
215+
this.loading = false
216+
this.$parent.$parent.close()
217+
this.parentFetchData()
218+
}).catch(error => {
219+
this.$notification.error({
220+
message: 'Failed to assign instance',
221+
description: error.response.data.assignvirtualmachineresponse.errortext && error.response.data.assignvirtualmachineresponse.errortext
222+
})
223+
this.$parent.$parent.close()
224+
this.parentFetchData()
225+
})
226+
}
227+
}
228+
}
229+
</script>
230+
231+
<style scoped lang="scss">
232+
.form {
233+
display: flex;
234+
flex-direction: column;
235+
236+
&__item {
237+
display: flex;
238+
flex-direction: column;
239+
width: 100%;
240+
margin-bottom: 10px;
241+
}
242+
243+
&__label {
244+
display: flex;
245+
font-weight: bold;
246+
margin-bottom: 5px;
247+
}
248+
249+
}
250+
251+
.submit-btn {
252+
margin-top: 10px;
253+
align-self: flex-end;
254+
}
255+
256+
.required {
257+
margin-right: 2px;
258+
color: red;
259+
font-size: 0.7rem;
260+
}
261+
262+
.loading {
263+
position: absolute;
264+
top: 0;
265+
right: 0;
266+
bottom: 0;
267+
left: 0;
268+
z-index: 1;
269+
display: flex;
270+
align-items: center;
271+
justify-content: center;
272+
font-size: 3rem;
273+
}
274+
</style>

0 commit comments

Comments
 (0)