setting: reusable component (#63)

Implements reusable settings component and the list view for global settings tab.

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Ritchie Vincent 2019-12-10 09:36:51 +00:00 committed by Rohit Yadav
parent c9a75e2f30
commit c6839a8550
11 changed files with 316 additions and 20 deletions

View File

@ -23,7 +23,6 @@ createPhysicalNetwork
createPortableIpRange
createPortForwardingRule
createPrivateGateway
createRolePermission
createSecondaryStagingStore
createSnapshotFromVMSnapshot
createStaticRoute
@ -50,7 +49,6 @@ deletePortableIpRange
deletePortForwardingRule
deletePrivateGateway
deleteProjectInvitation
deleteRolePermission
deleteSecondaryStagingStore
deleteSnapshotPolicies
deleteSslCert
@ -58,7 +56,6 @@ deleteStaticRoute
deleteStorageNetworkIpRange
deleteVlanIpRange
deleteVpnConnection
deleteVpnCustomerGateway
deleteVpnGateway
findHostsForMigration
findStoragePoolsForMigration
@ -74,8 +71,6 @@ listDedicatedHosts
listDedicatedPods
listDedicatedZones
listDeploymentPlanners
listDetailOptions
listDomainChildren
listEgressFirewallRules
listFirewallRules
listHostHAProviders
@ -95,7 +90,6 @@ listNetworkACLs
listNetworkServiceProviders
listNics
listOsCategories
listPhysicalNetworks
listPortableIpRanges
listPortForwardingRules
listPrivateGateways
@ -104,7 +98,6 @@ listProjectInvitations
listRegisteredServicePackages
listRemoteAccessVpns
listResourceLimits
listRolePermissions
listSamlAuthorization
listSecondaryStagingStores
listSnapshotPolicies
@ -134,7 +127,6 @@ revokeSecurityGroupEgress
revokeSecurityGroupIngress
startInternalLoadBalancerVM
stopInternalLoadBalancerVM
updateConfiguration
updateDefaultNicForVirtualMachine
updateLoadBalancerRule
updateNetworkACLItem
@ -143,9 +135,7 @@ updateNetworkServiceProvider
updatePhysicalNetwork
updateProjectInvitation
updateResourceLimit
updateRolePermission
updateTrafficType
updateVmNicIp
updateVpnCustomerGateway
uploadCustomCertificate
uploadSslCert

View File

@ -50,18 +50,18 @@
:dataSource="detailOptions[item.name]"
@change="val => handleInputChange(val, index)"
@pressEnter="e => updateDetail(index)" />
<a-button shape="circle" size="small" @click="updateDetail(index)" style="margin: 2px">
<a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" style="font-size: 24px"/>
</a-button>
<a-button shape="circle" size="small" @click="hideEditDetail(index)" style="margin: 2px">
<a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d" style="font-size: 24px"/>
</a-button>
</span>
<span v-else>{{ item.value }}</span>
</span>
</a-list-item-meta>
<div slot="actions">
<a-button shape="circle" @click="showEditDetail(index)">
<a-button shape="circle" size="default" @click="updateDetail(index)" v-if="item.edit">
<a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
</a-button>
<a-button shape="circle" size="default" @click="hideEditDetail(index)" v-if="item.edit">
<a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d" />
</a-button>
<a-button shape="circle" @click="showEditDetail(index)" v-if="!item.edit">
<a-icon type="edit" />
</a-button>
</div>

View File

@ -22,7 +22,6 @@
:columns="columns"
:dataSource="items"
:rowKey="record => record.id || record.name"
:scroll="{ x: '100%' }"
:pagination="false"
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
:rowClassName="getRowClassName"
@ -101,10 +100,44 @@
<a slot="zonename" slot-scope="text, record" href="javascript:;">
<router-link :to="{ path: '/zone/' + record.zoneid }">{{ text }}</router-link>
</a>
<template slot="value" slot-scope="text, record">
<a-input
v-if="editableValueKey === record.key"
:defaultValue="record.value"
v-model="editableValue"
@keydown.esc="editableValueKey = null"
@pressEnter="saveValue(record)">
</a-input>
<div v-else style="width: 200px; word-break: break-all">
{{ text }}
</div>
</template>
<template slot="actions" slot-scope="text, record">
<a-button
shape="circle"
v-if="editableValueKey !== record.key"
icon="edit"
@click="editValue(record)" />
<a-button
shape="circle"
@click="saveValue(record)"
v-if="editableValueKey === record.key" >
<a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
</a-button>
<a-button
shape="circle"
size="default"
@click="editableValueKey = null"
v-if="editableValueKey === record.key" >
<a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d" />
</a-button>
</template>
</a-table>
</template>
<script>
import { api } from '@/api'
import Console from '@/components/widgets/Console'
import Status from '@/components/widgets/Status'
@ -130,7 +163,9 @@ export default {
},
data () {
return {
selectedRowKeys: []
selectedRowKeys: [],
editableValueKey: null,
editableValue: ''
}
},
computed: {
@ -154,6 +189,30 @@ export default {
this.$store.dispatch('ToggleTheme', project.id === undefined ? 'light' : 'dark')
this.$message.success(`Switched to "${project.name}"`)
this.$router.push({ name: 'dashboard' })
},
saveValue (record) {
api('updateConfiguration', {
name: record.name,
value: this.editableValue
}).then(() => {
this.editableValueKey = null
this.$message.success('Setting Updated: ' + record.name)
this.$notification.warning({
message: 'Status',
description: 'Please restart your management server(s) for your new settings to take effect.'
})
}).catch(error => {
console.error(error)
this.$message.error('There was an error saving this setting.')
})
.finally(() => {
this.$emit('refresh')
})
},
editValue (record) {
this.editableValueKey = record.key
this.editableValue = record.value
}
}
}

View File

@ -0,0 +1,207 @@
<template>
<a-list size="large" class="list" :loading="loading">
<a-list-item :key="index" v-for="(item, index) in items" class="item">
<a-list-item-meta>
<span slot="title" style="word-break: break-all"><strong>{{ item.name }}</strong></span>
<span slot="description" style="word-break: break-all">{{ item.description }}</span>
</a-list-item-meta>
<div class="item__content">
<a-input
v-if="editableValueKey === index"
class="editable-value value"
:defaultValue="item.value"
v-model="editableValue"
@keydown.esc="editableValueKey = null"
@pressEnter="updateData(item)">
</a-input>
<span v-else class="value">
{{ item.value }}
</span>
</div>
<div slot="actions" class="action">
<a-button
shape="circle"
v-if="editableValueKey !== index"
icon="edit"
@click="setEditableSetting(item, index)" />
<a-button
shape="circle"
size="default"
@click="editableValueKey = null"
v-if="editableValueKey === index" >
<a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d" />
</a-button>
<a-button
shape="circle"
@click="updateData(item)"
v-if="editableValueKey === index" >
<a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
</a-button>
</div>
</a-list-item>
</a-list>
</template>
<script>
import { api } from '@/api'
export default {
name: 'SettingsTab',
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
required: true
}
},
data () {
return {
items: [],
scopeKey: '',
editableValueKey: null,
editableValue: ''
}
},
beforeMount () {
switch (this.$route.meta.name) {
case 'account':
this.scopeKey = 'accountid'
break
case 'domain':
this.scopeKey = 'domainid'
break
case 'zone':
this.scopeKey = 'zoneid'
break
case 'cluster':
this.scopeKey = 'clusterid'
break
case 'storagepool':
this.scopeKey = 'storageid'
break
case 'imagestore':
this.scopeKey = 'imagestoreuuid'
break
default:
this.scopeKey = ''
}
},
mounted () {
this.fetchData()
},
watch: {
resource: newItem => {
if (!newItem.id) return
this.fetchData()
}
},
methods: {
fetchData (callback) {
this.loading = true
api('listConfigurations', {
[this.scopeKey]: this.resource.id,
listAll: true
}).then(response => {
this.items = response.listconfigurationsresponse.configuration
}).catch(error => {
console.error(error)
this.$message.error('There was an error loading these settings.')
}).finally(() => {
this.loading = false
if (!callback) return
callback()
})
},
updateData (item) {
this.loading = true
api('updateConfiguration', {
[this.scopeKey]: this.resource.id,
name: item.name,
value: this.editableValue
}).then(() => {
this.$message.success('Setting ' + item.name + ' updated to ' + this.editableValue)
}).catch(error => {
console.error(error)
this.$message.error('There was an error saving this setting.')
this.$notification.error({
message: 'Error',
description: 'There was an error saving this setting. Please try again later.'
})
}).finally(() => {
this.loading = false
this.fetchData(() => {
this.editableValueKey = null
})
})
},
setEditableSetting (item, index) {
this.editableValueKey = index
this.editableValue = item.value
}
}
}
</script>
<style scoped lang="scss">
.list {
}
.editable-value {
@media (min-width: 760px) {
text-align: right;
margin-left: 40px;
margin-right: -40px;
}
}
.item {
display: flex;
flex-direction: column;
align-items: stretch;
@media (min-width: 760px) {
flex-direction: row;
}
&__content {
width: 100%;
display: flex;
word-break: break-all;
@media (min-width: 760px) {
width: auto;
}
}
}
.action {
margin-top: 20px;
margin-left: -12px;
@media (min-width: 480px) {
margin-left: -24px;
}
@media (min-width: 760px) {
margin-top: 0;
margin-left: 0;
}
}
.value {
margin-top: 20px;
@media (min-width: 760px) {
margin-top: 0;
}
}
</style>

View File

@ -26,7 +26,7 @@ export default {
title: 'Global Settings',
icon: 'setting',
permission: ['listConfigurations'],
columns: ['name', 'description', 'category', 'value'],
columns: ['name', 'description', 'category', 'value', 'actions'],
details: ['name', 'category', 'description', 'value']
},
{

View File

@ -90,6 +90,13 @@ export default {
title: 'Users',
param: 'account'
}],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
}, {
name: 'Settings',
component: () => import('@/components/view/SettingsTab.vue')
}],
actions: [
{
api: 'createAccount',
@ -185,6 +192,9 @@ export default {
{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
}, {
name: 'Settings',
component: () => import('@/components/view/SettingsTab.vue')
}
],
treeView: true,

View File

@ -27,6 +27,13 @@ export default {
title: 'Hosts',
param: 'clusterid'
}],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
}, {
name: 'Settings',
component: () => import('@/components/view/SettingsTab.vue')
}],
actions: [
{
api: 'addCluster',

View File

@ -27,6 +27,13 @@ export default {
title: 'Volumes',
param: 'storageid'
}],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
}, {
name: 'Settings',
component: () => import('@/components/view/SettingsTab.vue')
}],
actions: [
{
api: 'createStoragePool',

View File

@ -22,6 +22,13 @@ export default {
permission: ['listImageStores'],
columns: ['name', 'url', 'protocol', 'scope', 'zonename'],
details: ['name', 'id', 'url', 'protocol', 'provider', 'scope', 'zonename'],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
}, {
name: 'Settings',
component: () => import('@/components/view/SettingsTab.vue')
}],
actions: [
{
api: 'addImageStore',

View File

@ -51,6 +51,13 @@ export default {
title: 'Secondary Storage',
param: 'zoneid'
}],
tabs: [{
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
}, {
name: 'Settings',
component: () => import('@/components/view/SettingsTab.vue')
}],
actions: [
{
api: 'createZone',

View File

@ -202,6 +202,7 @@
:loading="loading"
:columns="columns"
:items="items"
@refresh="this.fetchData"
v-if="!treeView" />
<a-pagination
class="row-element"
@ -287,6 +288,7 @@ export default {
'$route' (to, from) {
if (to.fullPath !== from.fullPath && !to.fullPath.includes('action/')) {
this.page = 1
this.searchQuery = ''
this.fetchData()
}
},