Skip to content

Commit 28088aa

Browse files
components: reusable details settings and new fixes/features (#38)
- Reusable details settings component for VM and templates, with autocompletion of options - Instancegroup feature - split monitor into individual event and alert tabs, move alerts to infra - reimplement logo - project selection refactorings - use password input for relevant fields Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
1 parent 831b7e8 commit 28088aa

24 files changed

+560
-324
lines changed

ui/docs/api/apis.primate.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ rm -f apis.txt
33
grep api\( -R . | grep -v import | sed "s/.*api('//g" | sed "s/'.*//g" | grep -v '.vue' | sort | uniq >> apis.txt
44
grep api -R config | sed "s/.*api: '//g" | sed "s/'.*//g" | grep -v \.js | sort | uniq >> apis.txt
55
grep store.getters.apis -R . | sed "s/' in.*//g" | sed "s/').*//g" | grep "'" | sed "s/.*'//g" | grep -v ']' >> apis.txt
6-
grep 'permission:\ \[' -R config | sed "s/.*permission: \[ '//g" | grep -v .js | sed "s/', '/\\n/g" | sed "s/'.*//g" >> apis.txt
6+
grep 'permission:\ \[' -R config | sed "s/.*permission: \['//g" | grep -v .js | sed "s/', '/\\n/g" | sed "s/'.*//g" >> apis.txt
77
cat apis.txt | sort | uniq > apis.uniq
88
rm -f apis.txt
99
mv apis.uniq ../docs/api/apis.txt

ui/docs/api/apis.remaining

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ deleteVpnCustomerGateway
6262
deleteVpnGateway
6363
findHostsForMigration
6464
findStoragePoolsForMigration
65-
getUploadParamsForTemplate
6665
importLdapUsers
6766
ldapCreateAccount
6867
linkDomainToLdap

ui/src/assets/cloudmonkey.png

-29.6 KB
Binary file not shown.

ui/src/assets/logo.svg

Lines changed: 163 additions & 33 deletions
Loading

ui/src/components/header/HeaderNotice.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
overlayClassName="header-notice-popover">
2626
<template slot="content">
2727
<a-spin :spinning="loading">
28-
<a-list>
28+
<a-list style="min-width: 200px">
2929
<a-list-item>
3030
<a-list-item-meta title="Notifications">
3131
<a-avatar :style="{backgroundColor: '#6887d0', verticalAlign: 'middle'}" icon="notification" slot="avatar"/>

ui/src/components/header/Logo.vue

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,14 @@
1818
<template>
1919
<div class="logo">
2020
<img class="logo-image" src="~@/assets/logo.svg"/>
21-
<project-menu></project-menu>
2221
</div>
2322
</template>
2423

2524
<script>
26-
import ProjectMenu from './ProjectMenu'
2725
2826
export default {
2927
name: 'Logo',
3028
components: {
31-
ProjectMenu
3229
},
3330
props: {
3431
title: {
@@ -50,7 +47,6 @@ export default {
5047
height: 64px;
5148
position: relative;
5249
line-height: 64px;
53-
padding-left: 12px;
5450
-webkit-transition: all .3s;
5551
transition: all .3s;
5652
background: #002140;
@@ -63,8 +59,7 @@ export default {
6359
}
6460
6561
.logo-image {
66-
width: 54px;
67-
margin-right: 10px;
62+
width: 256px;
6863
display: inline-block;
6964
vertical-align: middle;
7065
}

ui/src/components/header/ProjectMenu.vue

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,43 @@
1616
// under the License.
1717

1818
<template>
19-
<span class="project-wrapper" :disabled="true">
20-
<a-select
21-
class="project-wrapper-select"
22-
size="default"
23-
defaultValue="Default View"
24-
:value="selectedProject"
25-
:disabled="isDisabled()"
26-
:filterOption="filterProject"
27-
@change="changeProject"
28-
showSearch>
29-
<a-select-option v-for="(project, index) in projects" :key="index">
30-
{{ project.displaytext || project.name }}
31-
</a-select-option>
32-
</a-select>
33-
</span>
19+
<a-popover
20+
class="project"
21+
v-model="visible"
22+
trigger="click"
23+
placement="bottom"
24+
:autoAdjustOverflow="true"
25+
:arrowPointAtCenter="true">
26+
<template slot="content">
27+
<a-menu style="margin: -12px -16px">
28+
<a-menu-item>
29+
<a-icon class="project-icon" type="login" />
30+
<a-select
31+
class="project-select"
32+
size="default"
33+
defaultValue="Default View"
34+
:value="selectedProject"
35+
:disabled="isDisabled()"
36+
:filterOption="filterProject"
37+
@change="changeProject"
38+
showSearch>
39+
<a-select-option v-for="(project, index) in projects" :key="index">
40+
{{ project.displaytext || project.name }}
41+
</a-select-option>
42+
</a-select>
43+
</a-menu-item>
44+
<a-menu-item>
45+
<router-link :to="{ path: '/project' }">
46+
<a-icon class="project-icon" type="project" />
47+
{{ $t('Projects') }}
48+
</router-link>
49+
</a-menu-item>
50+
</a-menu>
51+
</template>
52+
<span @click="visible = !visible" class="header-notice-opener">
53+
<a-icon class="project-icon" type="project" />
54+
</span>
55+
</a-popover>
3456
</template>
3557

3658
<script>
@@ -43,6 +65,7 @@ export default {
4365
name: 'ProjectMenu',
4466
data () {
4567
return {
68+
visible: false,
4669
projects: [],
4770
selectedProject: 'Default View'
4871
}
@@ -105,9 +128,16 @@ export default {
105128
</script>
106129

107130
<style lang="less" scoped>
108-
.project-wrapper {
131+
.project {
109132
&-select {
110-
width: 165px;
133+
width: 200px;
134+
}
135+
136+
&-icon {
137+
font-size: 20px;
138+
line-height: 1;
139+
padding-top: 5px;
140+
padding-right: 5px;
111141
}
112142
}
113143
</style>

ui/src/components/header/UserMenu.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<template>
1919
<div class="user-menu">
2020

21+
<project-menu class="action"/>
2122
<translation-menu class="action"/>
2223
<header-notice class="action"/>
2324
<a-dropdown>
@@ -59,12 +60,14 @@
5960
<script>
6061
import config from '@/config/settings'
6162
import HeaderNotice from './HeaderNotice'
63+
import ProjectMenu from './ProjectMenu'
6264
import TranslationMenu from './TranslationMenu'
6365
import { mapActions, mapGetters } from 'vuex'
6466
6567
export default {
6668
name: 'UserMenu',
6769
components: {
70+
ProjectMenu,
6871
TranslationMenu,
6972
HeaderNotice
7073
},
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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+
<a-spin :spinning="loading">
20+
<div v-show="!showAddDetail">
21+
<a-button type="dashed" style="width: 100%" icon="plus" @click="showAddDetail = true">Add Setting</a-button>
22+
</div>
23+
<div v-show="showAddDetail">
24+
<a-auto-complete
25+
style="width: 100%"
26+
:value="newKey"
27+
:dataSource="Object.keys(detailOptions)"
28+
placeholder="Name"
29+
@change="e => onAddInputChange(e, 'newKey')" />
30+
<a-auto-complete
31+
style="width: 100%"
32+
:value="newValue"
33+
:dataSource="detailOptions[newKey]"
34+
placeholder="Value"
35+
@change="e => onAddInputChange(e, 'newValue')" />
36+
<a-button type="dashed" style="width: 50%" icon="close" @click="showAddDetail = false">Cancel</a-button>
37+
<a-button type="primary" style="width: 50%" icon="plus" @click="addDetail">Add Setting</a-button>
38+
</div>
39+
<a-list size="large">
40+
<a-list-item :key="index" v-for="(item, index) in details">
41+
<a-list-item-meta>
42+
<span slot="title">{{ item.name }}</span>
43+
<span slot="description" style="word-break: break-all">
44+
<span v-if="item.edit" style="display: flex">
45+
<a-auto-complete
46+
style="width: 100%"
47+
:value="item.value"
48+
:dataSource="detailOptions[item.name]"
49+
@change="val => handleInputChange(val, index)"
50+
@pressEnter="e => updateDetail(index)" />
51+
<a-button shape="circle" size="small" @click="updateDetail(index)" style="margin: 2px">
52+
<a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" style="font-size: 24px"/>
53+
</a-button>
54+
<a-button shape="circle" size="small" @click="hideEditDetail(index)" style="margin: 2px">
55+
<a-icon type="close-circle" theme="twoTone" twoToneColor="#eb2f96" style="font-size: 24px"/>
56+
</a-button>
57+
</span>
58+
<span v-else>{{ item.value }}</span>
59+
</span>
60+
</a-list-item-meta>
61+
<div slot="actions">
62+
<a-button shape="circle" @click="showEditDetail(index)">
63+
<a-icon type="edit" />
64+
</a-button>
65+
</div>
66+
<div slot="actions">
67+
<a-popconfirm
68+
title="Delete setting?"
69+
@confirm="deleteDetail(index)"
70+
okText="Yes"
71+
cancelText="No"
72+
placement="left"
73+
>
74+
<a-button shape="circle">
75+
<a-icon type="delete" theme="twoTone" twoToneColor="#f5222d" />
76+
</a-button>
77+
</a-popconfirm>
78+
</div>
79+
</a-list-item>
80+
</a-list>
81+
</a-spin>
82+
</template>
83+
84+
<script>
85+
import { api } from '@/api'
86+
87+
export default {
88+
name: 'DetailSettings',
89+
props: {
90+
resource: {
91+
type: Object,
92+
required: true
93+
}
94+
},
95+
data () {
96+
return {
97+
details: [],
98+
detailOptions: {},
99+
showAddDetail: false,
100+
newKey: '',
101+
newValue: '',
102+
loading: false,
103+
resourceType: 'UserVm'
104+
}
105+
},
106+
watch: {
107+
resource: function (newItem, oldItem) {
108+
this.updateResource(newItem)
109+
}
110+
},
111+
mounted () {
112+
this.updateResource(this.resource)
113+
},
114+
methods: {
115+
updateResource (resource) {
116+
if (!resource) {
117+
return
118+
}
119+
this.resource = resource
120+
this.resourceType = this.$route.meta.resourceType
121+
if (!resource.details) {
122+
return
123+
}
124+
this.details = Object.keys(this.resource.details).map(k => {
125+
return { name: k, value: this.resource.details[k], edit: false }
126+
})
127+
api('listDetailOptions', { resourcetype: this.resourceType, resourceid: this.resource.id }).then(json => {
128+
this.detailOptions = json.listdetailoptionsresponse.detailoptions.details
129+
})
130+
},
131+
showEditDetail (index) {
132+
this.details[index].edit = true
133+
this.details[index].originalValue = this.details[index].value
134+
this.$set(this.details, index, this.details[index])
135+
},
136+
hideEditDetail (index) {
137+
this.details[index].edit = false
138+
this.details[index].value = this.details[index].originalValue
139+
this.$set(this.details, index, this.details[index])
140+
},
141+
handleInputChange (val, index) {
142+
this.details[index].value = val
143+
this.$set(this.details, index, this.details[index])
144+
},
145+
onAddInputChange (val, obj) {
146+
this[obj] = val
147+
},
148+
runApi () {
149+
var apiName = ''
150+
if (this.resourceType === 'UserVm') {
151+
apiName = 'updateVirtualMachine'
152+
} else if (this.resourceType === 'Template') {
153+
apiName = 'updateTemplate'
154+
}
155+
if (!(apiName in this.$store.getters.apis)) {
156+
this.$notification.error({
157+
message: 'Failed to execute API: ' + apiName,
158+
description: 'User is not permitted to use the API'
159+
})
160+
return
161+
}
162+
163+
const params = { id: this.resource.id }
164+
this.details.forEach(function (item, index) {
165+
params['details[0].' + item.name] = item.value
166+
})
167+
this.loading = true
168+
api(apiName, params).then(json => {
169+
var details = {}
170+
if (this.resourceType === 'UserVm') {
171+
details = json.updatevirtualmachineresponse.virtualmachine.details
172+
} else if (this.resourceType === 'Template') {
173+
details = json.updatetemplateresponse.template.details
174+
}
175+
this.details = Object.keys(details).map(k => {
176+
return { name: k, value: details[k], edit: false }
177+
})
178+
}).catch(error => {
179+
this.$notification.error({
180+
message: 'Failed to add setting',
181+
description: error.response.headers['x-description']
182+
})
183+
}).finally(f => {
184+
this.loading = false
185+
this.showAddDetail = false
186+
this.newKey = ''
187+
this.newValue = ''
188+
})
189+
},
190+
addDetail () {
191+
this.details.push({ name: this.newKey, value: this.newValue })
192+
this.runApi()
193+
},
194+
updateDetail (index) {
195+
this.runApi()
196+
},
197+
deleteDetail (index) {
198+
this.details.splice(index, 1)
199+
this.runApi()
200+
}
201+
}
202+
}
203+
</script>

ui/src/components/view/FormView.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,15 @@
6868
:placeholder="field.description"
6969
/>
7070
</span>
71-
<span v-else>
71+
<span v-else-if="field.name==='password'">
72+
<a-input-password
73+
v-decorator="[field.name, {
74+
rules: [{ required: field.required, message: 'Please enter input' }]
75+
}]"
76+
:placeholder="field.description"
77+
/>
78+
</span>
79+
<span v-else">
7280
<a-input
7381
v-decorator="[field.name, {
7482
rules: [{ required: field.required, message: 'Please enter input' }]

0 commit comments

Comments
 (0)