mirror of https://github.com/apache/cloudstack.git
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:
parent
c9a75e2f30
commit
c6839a8550
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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']
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue