You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2019/12/10 09:36:58 UTC
[cloudstack-primate] branch master updated: setting: reusable
component (#63)
This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git
The following commit(s) were added to refs/heads/master by this push:
new e7e4961 setting: reusable component (#63)
e7e4961 is described below
commit e7e49612de9e623776eb550b707191caf608f63b
Author: Ritchie Vincent <rf...@gmail.com>
AuthorDate: Tue Dec 10 09:36:51 2019 +0000
setting: reusable component (#63)
Implements reusable settings component and the list view for global settings tab.
Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
---
docs/api/apis.remaining | 10 --
src/components/view/DetailSettings.vue | 14 +-
src/components/view/ListView.vue | 63 +++++++-
src/components/view/SettingsTab.vue | 207 ++++++++++++++++++++++++++
src/config/section/config.js | 2 +-
src/config/section/iam.js | 10 ++
src/config/section/infra/clusters.js | 7 +
src/config/section/infra/primaryStorages.js | 7 +
src/config/section/infra/secondaryStorages.js | 7 +
src/config/section/infra/zones.js | 7 +
src/views/AutogenView.vue | 2 +
11 files changed, 316 insertions(+), 20 deletions(-)
diff --git a/docs/api/apis.remaining b/docs/api/apis.remaining
index 50fc784..0372047 100644
--- a/docs/api/apis.remaining
+++ b/docs/api/apis.remaining
@@ -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
diff --git a/src/components/view/DetailSettings.vue b/src/components/view/DetailSettings.vue
index 36cb528..0068b86 100644
--- a/src/components/view/DetailSettings.vue
+++ b/src/components/view/DetailSettings.vue
@@ -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>
diff --git a/src/components/view/ListView.vue b/src/components/view/ListView.vue
index 41a8ad5..e651992 100644
--- a/src/components/view/ListView.vue
+++ b/src/components/view/ListView.vue
@@ -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
}
}
}
diff --git a/src/components/view/SettingsTab.vue b/src/components/view/SettingsTab.vue
new file mode 100644
index 0000000..385008f
--- /dev/null
+++ b/src/components/view/SettingsTab.vue
@@ -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>
diff --git a/src/config/section/config.js b/src/config/section/config.js
index 26bf679..eff2869 100644
--- a/src/config/section/config.js
+++ b/src/config/section/config.js
@@ -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']
},
{
diff --git a/src/config/section/iam.js b/src/config/section/iam.js
index 351a871..468dcc8 100644
--- a/src/config/section/iam.js
+++ b/src/config/section/iam.js
@@ -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,
diff --git a/src/config/section/infra/clusters.js b/src/config/section/infra/clusters.js
index c9eeaae..03fbaaa 100644
--- a/src/config/section/infra/clusters.js
+++ b/src/config/section/infra/clusters.js
@@ -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',
diff --git a/src/config/section/infra/primaryStorages.js b/src/config/section/infra/primaryStorages.js
index bff2521..1c29a1f 100644
--- a/src/config/section/infra/primaryStorages.js
+++ b/src/config/section/infra/primaryStorages.js
@@ -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',
diff --git a/src/config/section/infra/secondaryStorages.js b/src/config/section/infra/secondaryStorages.js
index 3b81411..0deea35 100644
--- a/src/config/section/infra/secondaryStorages.js
+++ b/src/config/section/infra/secondaryStorages.js
@@ -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',
diff --git a/src/config/section/infra/zones.js b/src/config/section/infra/zones.js
index ee8d3fd..7e26bc4 100644
--- a/src/config/section/infra/zones.js
+++ b/src/config/section/infra/zones.js
@@ -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',
diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue
index 9c999d5..76e5937 100644
--- a/src/views/AutogenView.vue
+++ b/src/views/AutogenView.vue
@@ -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()
}
},