Skip to content

Commit 82f88d9

Browse files
committed
ui: initial work for erun custom action
Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 7f80ffb commit 82f88d9

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

ui/src/config/section/compute.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,15 @@ export default {
425425
dataView: true,
426426
show: (record, store) => { return ['Destroyed'].includes(record.state) && store.features.allowuserexpungerecovervm && record.vmtype !== 'sharedfsvm' }
427427
},
428+
{
429+
api: 'runCustomAction',
430+
icon: 'play-square-outlined',
431+
label: 'label.run.custom.action',
432+
dataView: true,
433+
component: shallowRef(defineAsyncComponent(() => import('@/views/extension/RunCustomAction'))),
434+
popup: true,
435+
show: (record) => { return ['External', 'Simulator'].includes(record.hypervisor) }
436+
},
428437
{
429438
api: 'unmanageVirtualMachine',
430439
icon: 'disconnect-outlined',
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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 class="form-layout" v-ctrl-enter="handleSubmit">
20+
<a-form
21+
:ref="formRef"
22+
:model="form"
23+
:rules="rules"
24+
:loading="loading"
25+
layout="vertical"
26+
@finish="handleSubmit">
27+
<a-form-item name="customactionid" ref="customactionid">
28+
<template #label>
29+
<tooltip-label :title="$t('label.customactionid')" :tooltip="apiParams.customactionid.description"/>
30+
</template>
31+
<a-select
32+
showSearch
33+
v-model:value="form.customactionid"
34+
:placeholder="apiParams.customactionid.description"
35+
optionFilterProp="label"
36+
:filterOption="(input, option) => {
37+
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
38+
}" >
39+
<a-select-option v-for="opt in customActions" :key="opt.id" :label="opt.name || opt.id">
40+
{{ opt.name || opt.id }}
41+
</a-select-option>
42+
</a-select>
43+
</a-form-item>
44+
<a-card v-if="form.customactionid" style="margin-bottom: 10px;">
45+
<div style="margin: 10px 0px;">{{ currentDescription }}</div>
46+
<a-divider />
47+
<div v-for="(field, fieldIndex) in currentParameters" :key="fieldIndex">
48+
<a-form-item :name="field.name" :ref="field.name">
49+
<template #label>
50+
<tooltip-label :title="$t('label.' + field.name)" :tooltip="$t('label.' + field.name)"/>
51+
</template>
52+
<a-switch
53+
v-if="field.type === 'BOOLEAN'"
54+
v-model:checked="form[field.name]"
55+
:placeholder="field.name"
56+
v-focus="fieldIndex === 0"
57+
/>
58+
<a-date-picker
59+
v-else-if="field.type === 'DATE'"
60+
show-time
61+
format="YYYY-MM-DD HH:mm:ss"
62+
:placeholder="field.name"
63+
v-focus="fieldIndex === 0"
64+
/>
65+
<a-input-number
66+
v-else-if="['FLOAT', 'INTEGER', 'SHORT', 'LONG'].includes(field.type)"
67+
:precision="['FLOAT'].includes(field.type) ? 2 : 0"
68+
v-focus="fieldIndex === 0"
69+
v-model:value="form[field.name]"
70+
:placeholder="field.name" />
71+
<a-input
72+
v-else
73+
v-focus="fieldIndex === 0"
74+
v-model:value="form[field.name]"
75+
:placeholder="field.name" />
76+
</a-form-item>
77+
</div>
78+
</a-card>
79+
<div :span="24" class="action-button">
80+
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
81+
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
82+
</div>
83+
</a-form>
84+
</div>
85+
</template>
86+
87+
<script>
88+
import { ref, reactive, toRaw } from 'vue'
89+
import { api } from '@/api'
90+
import TooltipLabel from '@/components/widgets/TooltipLabel'
91+
import DetailsInput from '@/components/widgets/DetailsInput'
92+
93+
export default {
94+
name: 'RunCustomAction',
95+
components: {
96+
TooltipLabel,
97+
DetailsInput
98+
},
99+
props: {
100+
resource: {
101+
type: Object,
102+
required: true
103+
}
104+
},
105+
data () {
106+
return {
107+
customActions: [],
108+
loading: false
109+
}
110+
},
111+
beforeCreate () {
112+
this.apiParams = this.$getApiParams('runCustomAction')
113+
},
114+
created () {
115+
this.initForm()
116+
this.fetchCustomActions()
117+
},
118+
computed: {
119+
resourceType () {
120+
const metaResourceType = this.$route.meta.resourceType
121+
if (metaResourceType && !['UserVm', 'DomainRouter', 'SystemVm'].includes(metaResourceType)) {
122+
return metaResourceType
123+
}
124+
return 'VirtualMachine'
125+
},
126+
currentAction () {
127+
if (!this.customActions || this.customActions.length === 0 || !this.form.customactionid) {
128+
return []
129+
}
130+
return this.customActions.find(i => i.id === this.form.customactionid)
131+
},
132+
currentDescription () {
133+
return this.currentAction?.description || ''
134+
},
135+
currentParameters () {
136+
return this.currentAction?.parameters || []
137+
}
138+
},
139+
methods: {
140+
initForm () {
141+
this.formRef = ref()
142+
this.form = reactive({})
143+
this.rules = reactive({
144+
customactionid: [{ required: true, message: `${this.$t('message.error.select')}` }]
145+
})
146+
},
147+
fetchCustomActions () {
148+
this.loading = true
149+
this.customActions = []
150+
const params = {
151+
resourcetype: this.resourceType,
152+
resourceid: this.resource.id
153+
}
154+
api('listCustomActions', params).then(json => {
155+
this.customActions = json?.listcustomactionsresponse?.extensioncustomaction || []
156+
}).finally(() => {
157+
this.loading = false
158+
})
159+
},
160+
handleSubmit (e) {
161+
e.preventDefault()
162+
if (this.loading) return
163+
this.formRef.value.validate().then(() => {
164+
const values = toRaw(this.form)
165+
this.loading = true
166+
const params = {
167+
resourcetype: this.resourceType,
168+
resourceid: this.resource.id,
169+
enabled: true
170+
}
171+
console.log('values----------------', values)
172+
for (const key of Object.keys(values)) {
173+
var value = values[key]
174+
if (value !== undefined && value != null &&
175+
(typeof value !== 'string' || (typeof value === 'string' && value.trim().length > 0))) {
176+
params[key] = value
177+
}
178+
}
179+
if (params) {
180+
console.log('----------------', params)
181+
return
182+
}
183+
api('runCustomAction', params).then(response => {
184+
this.$emit('refresh-data')
185+
this.$notification.success({
186+
message: this.$t('label.run.custom.action'),
187+
description: this.$t('message.success.run.custom.action')
188+
})
189+
this.closeAction()
190+
}).catch(error => {
191+
this.$notification.error({
192+
message: this.$t('message.request.failed'),
193+
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
194+
duration: 0
195+
})
196+
}).finally(() => {
197+
this.loading = false
198+
})
199+
}).catch(error => {
200+
this.$notifyError(error)
201+
})
202+
},
203+
closeAction () {
204+
this.$emit('close-action')
205+
}
206+
}
207+
}
208+
</script>
209+
210+
<style scoped lang="less">
211+
.form-layout {
212+
width: 80vw;
213+
@media (min-width: 600px) {
214+
width: 550px;
215+
}
216+
}
217+
</style>

0 commit comments

Comments
 (0)