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 2020/02/03 09:39:22 UTC

[cloudstack-primate] branch master updated: vpc: static routes tab (#139)

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 a06374b  vpc: static routes tab (#139)
a06374b is described below

commit a06374b4d3771379a2e6b366f5b0582be7950deb
Author: Ritchie Vincent <rf...@gmail.com>
AuthorDate: Mon Feb 3 09:39:12 2020 +0000

    vpc: static routes tab (#139)
    
    Adds static routes tab for private gateway.
    
    Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
    Co-authored-by: Rohit Yadav <ro...@apache.org>
---
 src/layouts/ResourceLayout.vue        |   4 +-
 src/locales/en.json                   |   2 +
 src/views/network/StaticRoutesTab.vue | 322 ++++++++++++++++++++++++++++++++--
 3 files changed, 313 insertions(+), 15 deletions(-)

diff --git a/src/layouts/ResourceLayout.vue b/src/layouts/ResourceLayout.vue
index 5382434..4a84f1d 100644
--- a/src/layouts/ResourceLayout.vue
+++ b/src/layouts/ResourceLayout.vue
@@ -18,11 +18,11 @@
 <template>
   <div class="page-header-index-wide page-header-wrapper-grid-content-main">
     <a-row :gutter="12">
-      <a-col :md="24" :lg="8" style="margin-bottom: 12px">
+      <a-col :md="24" :lg="7" style="margin-bottom: 12px">
         <slot name="left">
         </slot>
       </a-col>
-      <a-col :md="24" :lg="16">
+      <a-col :md="24" :lg="17">
         <slot name="right">
         </slot>
       </a-col>
diff --git a/src/locales/en.json b/src/locales/en.json
index 7869213..6125c41 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -467,6 +467,7 @@
 "label.add.primary.storage": "Add Primary Storage",
 "label.add.region": "Add Region",
 "label.add.role": "Add Role",
+"label.add.route": "Add Route",
 "label.add.rule": "Add Rule",
 "label.add.secondary.storage": "Add Secondary Storage",
 "label.add.security.group": "Add Security Group",
@@ -492,6 +493,7 @@
 "label.change.ipaddress": "Change IP address for NIC",
 "label.change.service.offering": "Change service offering",
 "label.change.value": "Change value",
+"label.cidr.destination.network": "Destination Network CIDR",
 "label.configure": "Configure",
 "label.configure.ldap": "Configure LDAP",
 "label.configure.vpc": "Configure VPC",
diff --git a/src/views/network/StaticRoutesTab.vue b/src/views/network/StaticRoutesTab.vue
index d96d38d..9c79684 100644
--- a/src/views/network/StaticRoutesTab.vue
+++ b/src/views/network/StaticRoutesTab.vue
@@ -16,24 +16,67 @@
 // under the License.
 
 <template>
-  <a-spin :spinning="fetchLoading">
-    Static Routes Stub
-    <a-button type="dashed" icon="plus" style="width: 100%" @click="handleOpenModal">Add: button to add static route</a-button>
-    <div v-for="(route, index) in routes" :key="index">
-      {{ route }}
+  <a-spin :spinning="componentLoading">
+    <div class="new-route">
+      <a-input v-model="newRoute" icon="plus" :placeholder="$t('label.cidr.destination.network')"></a-input>
+      <a-button type="primary" @click="handleAdd">{{ $t('label.add.route') }}</a-button>
     </div>
+
+    <div class="list">
+      <div v-for="(route, index) in routes" :key="index" class="list__item">
+        <div class="list__col">
+          <div class="list__label">{{ $t('label.cidr.destination.network') }}</div>
+          <div>{{ route.cidr }}</div>
+        </div>
+        <div class="actions">
+          <a-button shape="round" icon="tag" @click="() => openTagsModal(route)"></a-button>
+          <a-button shape="round" icon="delete" type="danger" @click="() => handleDelete(route)"></a-button>
+        </div>
+      </div>
+    </div>
+
+    <a-modal title="Edit Tags" v-model="tagsModalVisible" :footer="null">
+      <a-spin v-if="tagsLoading"></a-spin>
+
+      <div v-else>
+        <a-form :form="newTagsForm" class="add-tags" @submit="handleAddTag">
+          <div class="add-tags__input">
+            <p class="add-tags__label">{{ $t('key') }}</p>
+            <a-form-item>
+              <a-input v-decorator="['key', { rules: [{ required: true, message: 'Please specify a tag key'}] }]" />
+            </a-form-item>
+          </div>
+          <div class="add-tags__input">
+            <p class="add-tags__label">{{ $t('value') }}</p>
+            <a-form-item>
+              <a-input v-decorator="['value', { rules: [{ required: true, message: 'Please specify a tag value'}] }]" />
+            </a-form-item>
+          </div>
+          <a-button type="primary" html-type="submit">{{ $t('label.add') }}</a-button>
+        </a-form>
+
+        <a-divider style="margin-top: 0;"></a-divider>
+
+        <div class="tags-container">
+          <div class="tags" v-for="(tag, index) in tags" :key="index">
+            <a-tag :key="index" :closable="true" :afterClose="() => handleDeleteTag(tag)">
+              {{ tag.key }} = {{ tag.value }}
+            </a-tag>
+          </div>
+        </div>
+
+        <a-button class="add-tags-done" @click="tagsModalVisible = false" type="primary">{{ $t('OK') }}</a-button>
+      </div>
+
+    </a-modal>
   </a-spin>
 </template>
 
 <script>
 import { api } from '@/api'
-import Status from '@/components/widgets/Status'
 
 export default {
   name: 'StaticRoutesTab',
-  components: {
-    Status
-  },
   props: {
     resource: {
       type: Object,
@@ -47,7 +90,13 @@ export default {
   data () {
     return {
       routes: [],
-      fetchLoading: false
+      componentLoading: false,
+      selectedRule: null,
+      tagsModalVisible: false,
+      newTagsForm: this.$form.createForm(this),
+      tags: [],
+      tagsLoading: false,
+      newRoute: null
     }
   },
   mounted () {
@@ -62,7 +111,7 @@ export default {
   },
   methods: {
     fetchData () {
-      this.fetchLoading = true
+      this.componentLoading = true
       api('listStaticRoutes', { gatewayid: this.resource.id }).then(json => {
         this.routes = json.liststaticroutesresponse.staticroute
       }).catch(error => {
@@ -71,13 +120,260 @@ export default {
           description: error.response.headers['x-description']
         })
       }).finally(() => {
-        this.fetchLoading = false
+        this.componentLoading = false
+      })
+    },
+    handleAdd () {
+      if (!this.newRoute) return
+
+      this.componentLoading = true
+      api('createStaticRoute', {
+        cidr: this.newRoute,
+        gatewayid: this.resource.id
+      }).then(response => {
+        this.$pollJob({
+          jobId: response.createstaticrouteresponse.jobid,
+          successMethod: () => {
+            this.fetchData()
+            this.$store.dispatch('AddAsyncJob', {
+              title: 'Successfully added static route',
+              jobid: response.createstaticrouteresponse.jobid,
+              status: 'progress'
+            })
+            this.componentLoading = false
+            this.newRoute = null
+          },
+          errorMessage: 'Failed to add static route',
+          errorMethod: () => {
+            this.fetchData()
+            this.componentLoading = false
+          },
+          loadingMessage: `Adding static route...`,
+          catchMessage: 'Error encountered while fetching async job result',
+          catchMethod: () => {
+            this.fetchData()
+            this.componentLoading = false
+          }
+        })
+      }).catch(error => {
+        this.$notification.error({
+          message: `Error ${error.response.status}`,
+          description: error.response.headers['x-description']
+        })
+        this.fetchData()
+        this.componentLoading = false
+      })
+    },
+    handleDelete (route) {
+      this.componentLoading = true
+      api('deleteStaticRoute', {
+        id: route.id
+      }).then(response => {
+        this.$pollJob({
+          jobId: response.deletestaticrouteresponse.jobid,
+          successMethod: () => {
+            this.fetchData()
+            this.$store.dispatch('AddAsyncJob', {
+              title: 'Successfully deleted static route',
+              jobid: response.deletestaticrouteresponse.jobid,
+              status: 'progress'
+            })
+            this.componentLoading = false
+          },
+          errorMessage: 'Failed to delete static route',
+          errorMethod: () => {
+            this.fetchData()
+            this.componentLoading = false
+          },
+          loadingMessage: `Deleting static route...`,
+          catchMessage: 'Error encountered while fetching async job result',
+          catchMethod: () => {
+            this.fetchData()
+            this.componentLoading = false
+          }
+        })
+      }).catch(error => {
+        this.$notification.error({
+          message: `Error ${error.response.status}`,
+          description: error.response.headers['x-description']
+        })
+        this.fetchData()
+        this.componentLoading = false
+      })
+    },
+    fetchTags (route) {
+      api('listTags', {
+        resourceId: route.id,
+        resourceType: 'StaticRoute',
+        listAll: true
+      }).then(response => {
+        this.tags = response.listtagsresponse.tag
+      }).catch(error => {
+        this.$notification.error({
+          message: `Error ${error.response.status}`,
+          description: error.response.data.errorresponse.errortext
+        })
+      })
+    },
+    handleDeleteTag (tag) {
+      this.tagsLoading = true
+      api('deleteTags', {
+        'tags[0].key': tag.key,
+        'tags[0].value': tag.value,
+        resourceIds: this.selectedRule.id,
+        resourceType: 'StaticRoute'
+      }).then(response => {
+        this.$pollJob({
+          jobId: response.deletetagsresponse.jobid,
+          successMessage: `Successfully deleted tag`,
+          successMethod: () => {
+            this.fetchTags(this.selectedRule)
+            this.tagsLoading = false
+          },
+          errorMessage: 'Failed to delete tag',
+          errorMethod: () => {
+            this.fetchTags(this.selectedRule)
+            this.tagsLoading = false
+          },
+          loadingMessage: `Deleting tag...`,
+          catchMessage: 'Error encountered while fetching async job result',
+          catchMethod: () => {
+            this.fetchTags(this.selectedRule)
+            this.tagsLoading = false
+          }
+        })
+      }).catch(error => {
+        this.$notification.error({
+          message: `Error ${error.response.status}`,
+          description: error.response.data.deletetagsresponse.errortext
+        })
+        this.tagsLoading = false
       })
+    },
+    handleAddTag (e) {
+      this.tagsLoading = true
+
+      e.preventDefault()
+      this.newTagsForm.validateFields((err, values) => {
+        if (err) {
+          this.tagsLoading = false
+          return
+        }
+
+        api('createTags', {
+          'tags[0].key': values.key,
+          'tags[0].value': values.value,
+          resourceIds: this.selectedRule.id,
+          resourceType: 'StaticRoute'
+        }).then(response => {
+          this.$pollJob({
+            jobId: response.createtagsresponse.jobid,
+            successMessage: `Successfully added new tag`,
+            successMethod: () => {
+              this.fetchTags(this.selectedRule)
+              this.tagsLoading = false
+            },
+            errorMessage: 'Failed to add new tag',
+            errorMethod: () => {
+              this.fetchTags(this.selectedRule)
+              this.tagsLoading = false
+            },
+            loadingMessage: `Adding new tag...`,
+            catchMessage: 'Error encountered while fetching async job result',
+            catchMethod: () => {
+              this.fetchTags(this.selectedRule)
+              this.tagsLoading = false
+            }
+          })
+        }).catch(error => {
+          this.$notification.error({
+            message: `Error ${error.response.status}`,
+            description: error.response.data.createtagsresponse.errortext
+          })
+          this.tagsLoading = false
+        })
+      })
+    },
+    openTagsModal (route) {
+      this.selectedRule = route
+      this.newTagsForm.resetFields()
+      this.fetchTags(this.selectedRule)
+      this.tagsModalVisible = true
     }
   }
 }
 </script>
 
-<style lang="less" scoped>
+<style lang="scss" scoped>
+
+  .list {
+    padding-top: 20px;
+
+    &__item {
+      display: flex;
+      justify-content: space-between;
+
+      &:not(:last-child) {
+        margin-bottom: 20px;
+      }
+    }
+
+    &__label {
+      font-weight: bold;
+    }
+
+  }
+
+  .actions {
+    display: flex;
+
+    button {
+      &:not(:last-child) {
+        margin-right: 10px;
+      }
+    }
+
+  }
+
+  .tags {
+    margin-bottom: 10px;
+  }
+  .add-tags {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    &__input {
+      margin-right: 10px;
+    }
+    &__label {
+      margin-bottom: 5px;
+      font-weight: bold;
+    }
+  }
+  .tags-container {
+    display: flex;
+    flex-wrap: wrap;
+    margin-bottom: 10px;
+  }
+  .add-tags-done {
+    display: block;
+    margin-left: auto;
+  }
+
+  .new-route {
+    display: flex;
+    padding-top: 10px;
+
+    input {
+      margin-right: 10px;
+    }
+
+    button {
+      &:not(:last-child) {
+        margin-right: 10px;
+      }
+    }
+
+  }
 
 </style>