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()
       }
     },