Skip to content

Commit 83f5cb8

Browse files
committed
changes
Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 0a16872 commit 83f5cb8

File tree

3 files changed

+187
-85
lines changed

3 files changed

+187
-85
lines changed

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ public class CreateExtensionCmd extends BaseCmd {
5757
description = "Type of the extension")
5858
private String type;
5959

60-
@Parameter(name = ApiConstants.EXTERNAL_DETAILS, type = CommandType.MAP,
61-
description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue")
62-
protected Map externalDetails;
60+
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP,
61+
description = "Details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].endpoint.url=urlvalue")
62+
protected Map details;
6363

6464
/////////////////////////////////////////////////////
6565
/////////////////// Accessors ///////////////////////
@@ -74,16 +74,11 @@ public String getType() {
7474
}
7575

7676
public Map<String, String> getExternalDetails() {
77-
Map<String, String> customparameterMap = convertDetailsToMap(externalDetails);
78-
Map<String, String> details = new HashMap<>();
79-
for (String key : customparameterMap.keySet()) {
80-
String value = customparameterMap.get(key);
81-
details.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + key, value);
77+
Map<String, String> customParameterMap = convertDetailsToMap(details);
78+
Map<String, String> externalDetails = new HashMap<>(customParameterMap.size());
79+
for (Map.Entry<String, String> entry : customParameterMap.entrySet()) {
80+
externalDetails.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + entry.getKey(), entry.getValue());
8281
}
83-
return details;
84-
}
85-
86-
public Map getDetails() {
8782
return externalDetails;
8883
}
8984

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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+
<div class="input-row">
21+
<a-input v-model:value="newKey" placeholder="Key" class="input-field" />
22+
<a-input v-model:value="newValue" placeholder="Value" class="input-field" />
23+
<a-button type="primary" class="add-button" @click="addEntry" :disabled="!newKey || !newValue">
24+
Add
25+
</a-button>
26+
</div>
27+
28+
<a-table
29+
:columns="columns"
30+
:dataSource="tableData"
31+
rowKey="key"
32+
size="small"
33+
:pagination="false"
34+
:showHeader="showTableHeaders"
35+
class="table"
36+
>
37+
<template #bodyCell="{ column, record }">
38+
<template v-if="column.key === 'action'">
39+
<template v-if="record.editing">
40+
<div class="flex-gap">
41+
<tooltip-button :tooltip="$t('label.ok')" icon="check-outlined" @onClick="saveEdit(record)" />
42+
<tooltip-button :tooltip="$t('label.cancel')" icon="close-outlined" @onClick="cancelEdit(record)" />
43+
</div>
44+
</template>
45+
<template v-else>
46+
<div class="flex-gap">
47+
<tooltip-button :tooltip="$t('label.edit')" icon="edit-outlined" @onClick="editRow(record)" />
48+
<tooltip-button type="danger" :tooltip="$t('label.remove')" icon="delete-outlined" @onClick="removeEntry(record.key)" />
49+
</div>
50+
</template>
51+
</template>
52+
53+
<template v-else-if="record.editing">
54+
<a-input v-model:value="record[column.key]" />
55+
</template>
56+
57+
<template v-else>
58+
{{ record[column.key] }}
59+
</template>
60+
</template>
61+
</a-table>
62+
</div>
63+
</template>
64+
65+
<script>
66+
import TooltipButton from '@/components/widgets/TooltipButton'
67+
68+
export default {
69+
name: 'DetailsInput',
70+
components: { TooltipButton },
71+
props: {
72+
value: {
73+
type: Object,
74+
default: () => ({})
75+
},
76+
showTableHeaders: {
77+
type: Boolean,
78+
default: true
79+
}
80+
},
81+
data () {
82+
return {
83+
columns: [
84+
{ title: 'Key', dataIndex: 'key', key: 'key', width: '40%' },
85+
{ title: 'Value', dataIndex: 'value', key: 'value', width: '40%' },
86+
{ title: 'Action', key: 'action', width: '20%' }
87+
],
88+
newKey: '',
89+
newValue: '',
90+
tableData: []
91+
}
92+
},
93+
watch: {
94+
value (newVal) {
95+
this.tableData = Object.entries(newVal || {}).map(([key, value]) => ({
96+
key,
97+
value,
98+
editing: false
99+
}))
100+
}
101+
},
102+
emits: ['update:value'],
103+
methods: {
104+
addEntry () {
105+
if (!this.newKey || !this.newValue) return
106+
const existingIndex = this.tableData.findIndex(row => row.key === this.newKey)
107+
if (existingIndex !== -1) {
108+
this.tableData[existingIndex].value = this.newValue
109+
} else {
110+
this.tableData.push({ key: this.newKey, value: this.newValue, editing: false })
111+
}
112+
this.updateData()
113+
this.newKey = ''
114+
this.newValue = ''
115+
},
116+
removeEntry (key) {
117+
this.tableData = this.tableData.filter(item => item.key !== key)
118+
this.updateData()
119+
},
120+
editRow (record) {
121+
record._originalValue = record.value
122+
record.editing = true
123+
},
124+
cancelEdit (record) {
125+
record.value = record._originalValue
126+
record.editing = false
127+
delete record._originalValue
128+
},
129+
saveEdit (record) {
130+
record.editing = false
131+
delete record._originalValue
132+
this.updateData()
133+
},
134+
updateData () {
135+
const obj = {}
136+
this.tableData.forEach(({ key, value }) => {
137+
obj[key] = value
138+
})
139+
this.$emit('update:value', obj)
140+
}
141+
}
142+
}
143+
</script>
144+
<style lang="css" scoped>
145+
.input-row {
146+
display: flex;
147+
gap: 8px;
148+
margin-bottom: 16px;
149+
}
150+
151+
.input-field {
152+
flex: 1; /* each takes half of the remaining 80% */
153+
}
154+
155+
.add-button {
156+
width: 20%;
157+
min-width: 100px; /* optional: prevents it from getting too narrow */
158+
}
159+
160+
.table {
161+
width: 100%;
162+
}
163+
164+
.flex-gap {
165+
display: flex;
166+
gap: 8px;
167+
}
168+
</style>

ui/src/views/extension/CreateExtension.vue

Lines changed: 12 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -52,53 +52,12 @@
5252
</a-form-item>
5353

5454
<a-form-item>
55-
<br />
56-
<span>{{ $t('message.add.external.details') }}</span><br/>
57-
<br />
58-
<a-button style="width: 100%" ref="details" type="primary" @click="addExternalDetails">
59-
<template #icon><plus-outlined /></template>
60-
{{ $t('label.add.external.details') }}
61-
</a-button>
62-
<a-form-item>
63-
<div v-show="showAddDetail">
64-
<br/>
65-
<a-input-group
66-
type="text"
67-
compact>
68-
<a-input
69-
style="width: 25%;"
70-
ref="keyElm"
71-
v-model:value="newKey"
72-
:placeholder="$t('label.name')"
73-
@change="e => onAddInputChange(e, 'newKey')" />
74-
<a-input
75-
class="tag-disabled-input"
76-
style=" width: 30px; margin-left: 10px; margin-right: 10px; pointer-events: none; text-align: center"
77-
placeholder="="
78-
disabled />
79-
<a-input
80-
style="width: 35%;"
81-
v-model:value="newValue"
82-
:placeholder="$t('label.value')"
83-
@change="e => onAddInputChange(e, 'newValue')" />
84-
<tooltip-button :tooltip="$t('label.add.setting')" :shape="null" icon="check-outlined" @onClick="addDetail" buttonClass="detail-button" />
85-
<tooltip-button :tooltip="$t('label.cancel')" :shape="null" icon="close-outlined" @onClick="closeDetail" buttonClass="detail-button" />
86-
</a-input-group>
87-
</div>
88-
</a-form-item>
89-
<a-list size="medium">
90-
<a-list-item :key="index" v-for="(item, index) in externalDetails">
91-
<span style="padding-left: 11px; width: 14%;"> {{ item.name }} </span>
92-
<span style="padding-left: 30px; width: 55%;"> {{ item.value }}</span>
93-
<tooltip-button
94-
style="width: 30%;"
95-
:tooltip="$t('label.delete')"
96-
:type="primary"
97-
:danger="true"
98-
icon="delete-outlined"
99-
@onClick="removeDetail(index)"/>
100-
</a-list-item>
101-
</a-list>
55+
<template #label>
56+
<tooltip-label :title="$t('label.details')" :tooltip="apiParams.details.description"/>
57+
</template>
58+
<div style="margin-bottom: 10px">{{ $t('message.add.details') }}</div>
59+
<details-input
60+
v-model:value="form.details" />
10261
</a-form-item>
10362
<div :span="24" class="action-button">
10463
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
@@ -113,12 +72,14 @@ import { ref, reactive, toRaw } from 'vue'
11372
import { api } from '@/api'
11473
import TooltipLabel from '@/components/widgets/TooltipLabel'
11574
import TooltipButton from '@/components/widgets/TooltipButton'
75+
import DetailsInput from '@/components/widgets/DetailsInput'
11676
11777
export default {
11878
name: 'CreateExtension',
11979
components: {
12080
TooltipLabel,
121-
TooltipButton
81+
TooltipButton,
82+
DetailsInput
12283
},
12384
data () {
12485
return {
@@ -142,28 +103,6 @@ export default {
142103
this.formRef = ref()
143104
this.form = reactive({})
144105
},
145-
addExternalDetails () {
146-
this.showAddDetail = true
147-
},
148-
onAddInputChange (val, obj) {
149-
this.error = false
150-
this[obj].concat(val.data)
151-
},
152-
addDetail () {
153-
if (this.newKey === '' || this.newValue === '') {
154-
this.error = this.$t('message.error.provide.setting')
155-
return
156-
}
157-
this.error = false
158-
this.externalDetails.push({ name: this.newKey, value: this.newValue })
159-
this.newKey = ''
160-
this.newValue = ''
161-
},
162-
removeDetail (index) {
163-
this.externalDetails.splice(index, 1)
164-
this.newKey = ''
165-
this.newValue = ''
166-
},
167106
fetchExtensionTypes () {
168107
const extensionTypes = []
169108
extensionTypes.push({
@@ -182,8 +121,8 @@ export default {
182121
name: values.name,
183122
type: values.type
184123
}
185-
if (this.externalDetails.length > 0) {
186-
this.externalDetails.forEach(function (item, index) {
124+
if (values.details.length > 0) {
125+
values.details.forEach((item) => {
187126
params['externaldetails[0].' + item.name] = item.value
188127
})
189128
}
@@ -218,7 +157,7 @@ export default {
218157
.form-layout {
219158
width: 80vw;
220159
@media (min-width: 600px) {
221-
width: 450px;
160+
width: 550px;
222161
}
223162
}
224163
</style>

0 commit comments

Comments
 (0)