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 2021/02/25 16:31:55 UTC

[cloudstack] branch 4.15 updated: ui: Add guest IP ranges (#4716)

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch 4.15
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.15 by this push:
     new a234501  ui: Add guest IP ranges (#4716)
a234501 is described below

commit a234501172a7948c13a50eb0ae87e8f1d1e68d0b
Author: Hoang Nguyen <ho...@unitech.vn>
AuthorDate: Thu Feb 25 23:31:40 2021 +0700

    ui: Add guest IP ranges (#4716)
    
    Fixes #4697
---
 ui/src/config/section/network.js                |   4 +
 ui/src/views/infra/network/IpRangesTabGuest.vue |   8 +-
 ui/src/views/infra/network/TrafficTypesTab.vue  |   4 +-
 ui/src/views/network/CreateVlanIpRange.vue      | 285 ++++++++++++++++++++++++
 ui/src/views/network/GuestIpRanges.vue          | 196 ++++++++++++++++
 5 files changed, 495 insertions(+), 2 deletions(-)

diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js
index 29b57e2..e3fc40f 100644
--- a/ui/src/config/section/network.js
+++ b/ui/src/config/section/network.js
@@ -53,6 +53,10 @@ export default {
         name: 'virtual.routers',
         component: () => import('@/views/network/RoutersTab.vue'),
         show: (record) => { return (record.type === 'Isolated' || record.type === 'Shared') && 'listRouters' in store.getters.apis }
+      }, {
+        name: 'guest.ip.range',
+        component: () => import('@/views/network/GuestIpRanges.vue'),
+        show: (record) => { return 'listVlanIpRanges' in store.getters.apis && (record.type === 'Shared' || (record.service && record.service.filter(x => x.name === 'SourceNat').count === 0)) }
       }],
       actions: [
         {
diff --git a/ui/src/views/infra/network/IpRangesTabGuest.vue b/ui/src/views/infra/network/IpRangesTabGuest.vue
index f70d7a5..49d63f7 100644
--- a/ui/src/views/infra/network/IpRangesTabGuest.vue
+++ b/ui/src/views/infra/network/IpRangesTabGuest.vue
@@ -34,6 +34,11 @@
       :rowKey="record => record.id"
       :pagination="false"
     >
+      <template slot="name" slot-scope="text, item">
+        <router-link :to="{ path: '/guestnetwork/' + item.id }">
+          {{ text }}
+        </router-link>
+      </template>
     </a-table>
     <a-pagination
       class="row-element pagination"
@@ -98,7 +103,8 @@ export default {
       columns: [
         {
           title: this.$t('label.name'),
-          dataIndex: 'name'
+          dataIndex: 'name',
+          scopedSlots: { customRender: 'name' }
         },
         {
           title: this.$t('label.type'),
diff --git a/ui/src/views/infra/network/TrafficTypesTab.vue b/ui/src/views/infra/network/TrafficTypesTab.vue
index d7f6e93..3b14f9c 100644
--- a/ui/src/views/infra/network/TrafficTypesTab.vue
+++ b/ui/src/views/infra/network/TrafficTypesTab.vue
@@ -90,7 +90,9 @@ export default {
     }
   },
   mounted () {
-    this.fetchData()
+    if (this.resource.id) {
+      this.fetchData()
+    }
   },
   watch: {
     loading (newData, oldData) {
diff --git a/ui/src/views/network/CreateVlanIpRange.vue b/ui/src/views/network/CreateVlanIpRange.vue
new file mode 100644
index 0000000..8a4be18
--- /dev/null
+++ b/ui/src/views/network/CreateVlanIpRange.vue
@@ -0,0 +1,285 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <a-spin :spinning="loading">
+    <div class="form-layout">
+      <div class="form">
+        <a-form
+          :form="form"
+          @submit="handleSubmit"
+          layout="vertical">
+          <a-form-item>
+            <span slot="label">
+              {{ $t('label.gateway') }}
+              <a-tooltip :title="apiParams.gateway.description">
+                <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+              </a-tooltip>
+            </span>
+            <a-input
+              v-decorator="['gateway', {
+                rules: [{ required: true, message: $t('message.error.gateway') }]
+              }]"
+              :placeholder="apiParams.gateway.description"/>
+          </a-form-item>
+          <a-form-item>
+            <span slot="label">
+              {{ $t('label.netmask') }}
+              <a-tooltip :title="apiParams.netmask.description">
+                <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+              </a-tooltip>
+            </span>
+            <a-input
+              v-decorator="['netmask', {
+                rules: [{ required: true, message: $t('message.error.netmask') }]
+              }]"
+              :placeholder="apiParams.netmask.description"/>
+          </a-form-item>
+          <a-row :gutter="12">
+            <a-col :md="12" lg="12">
+              <a-form-item>
+                <span slot="label">
+                  {{ $t('label.startipv4') }}
+                  <a-tooltip :title="apiParams.startip.description">
+                    <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+                  </a-tooltip>
+                </span>
+                <a-input
+                  v-decorator="['startip', {
+                    rules: [
+                      { required: true, message: $t('message.error.startip') },
+                      {
+                        validator: checkIpFormat,
+                        ipV4: true,
+                        message: $t('message.error.ipv4.address')
+                      }
+                    ]
+                  }]"
+                  :placeholder="apiParams.startip.description"/>
+              </a-form-item>
+            </a-col>
+            <a-col :md="12" :lg="12">
+              <a-form-item>
+                <span slot="label">
+                  {{ $t('label.endipv4') }}
+                  <a-tooltip :title="apiParams.endip.description">
+                    <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+                  </a-tooltip>
+                </span>
+                <a-input
+                  v-decorator="['endip', {
+                    rules: [
+                      { required: true, message: $t('message.error.endip') },
+                      {
+                        validator: checkIpFormat,
+                        ipV4: true,
+                        message: $t('message.error.ipv4.address')
+                      }
+                    ]
+                  }]"
+                  :placeholder="apiParams.endip.description"/>
+              </a-form-item>
+            </a-col>
+          </a-row>
+          <a-form-item>
+            <span slot="label">
+              {{ $t('label.ip6cidr') }}
+              <a-tooltip :title="apiParams.ip6cidr.description">
+                <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+              </a-tooltip>
+            </span>
+            <a-input
+              v-decorator="['ip6cidr']"
+              :placeholder="apiParams.ip6cidr.description"/>
+          </a-form-item>
+          <a-form-item>
+            <span slot="label">
+              {{ $t('label.ip6gateway') }}
+              <a-tooltip :title="apiParams.ip6gateway.description">
+                <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+              </a-tooltip>
+            </span>
+            <a-input
+              v-decorator="['ip6gateway']"
+              :placeholder="apiParams.ip6gateway.description"/>
+          </a-form-item>
+          <a-row :gutter="12">
+            <a-col :md="12" :lg="12">
+              <a-form-item>
+                <span slot="label">
+                  {{ $t('label.startipv6') }}
+                  <a-tooltip :title="apiParams.startipv6.description">
+                    <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+                  </a-tooltip>
+                </span>
+                <a-input
+                  v-decorator="['startipv6', {
+                    rules: [
+                      {
+                        validator: checkIpFormat,
+                        ipV6: true,
+                        message: $t('message.error.ipv6.address')
+                      }
+                    ]
+                  }]"
+                  :placeholder="apiParams.startipv6.description"/>
+              </a-form-item>
+            </a-col>
+            <a-col :md="12" :lg="12">
+              <a-form-item>
+                <span slot="label">
+                  {{ $t('label.endipv6') }}
+                  <a-tooltip :title="apiParams.endipv6.description">
+                    <a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
+                  </a-tooltip>
+                </span>
+                <a-input
+                  v-decorator="['endipv6', {
+                    rules: [
+                      {
+                        validator: checkIpFormat,
+                        ipV6: true,
+                        message: $t('message.error.ipv6.address')
+                      }
+                    ]
+                  }]"
+                  :placeholder="apiParams.endip.description"/>
+              </a-form-item>
+            </a-col>
+          </a-row>
+          <div :span="24" class="action-button">
+            <a-button
+              :loading="loading"
+              @click="closeAction">
+              {{ this.$t('label.cancel') }}
+            </a-button>
+            <a-button
+              :loading="loading"
+              type="primary"
+              @click="handleSubmit">
+              {{ this.$t('label.ok') }}
+            </a-button>
+          </div>
+        </a-form>
+      </div>
+    </div>
+  </a-spin>
+</template>
+
+<script>
+import { api } from '@/api'
+
+export default {
+  name: 'CreateVlanIpRange',
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      loading: false,
+      ipV4Regex: /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i,
+      ipV6Regex: /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([ [...]
+    }
+  },
+  created () {
+    this.form = this.$form.createForm(this)
+    this.apiConfig = this.$store.getters.apis.createVlanIpRange || {}
+    this.apiParams = {}
+    this.apiConfig.params.forEach(param => {
+      this.apiParams[param.name] = param
+    })
+  },
+  methods: {
+    handleSubmit (e) {
+      e.preventDefault()
+
+      this.form.validateFields((err, values) => {
+        if (err) {
+          return
+        }
+
+        const params = {}
+        params.forVirtualNetwork = false
+        params.networkid = this.resource.id
+        params.gateway = values.gateway
+        params.netmask = values.netmask
+        params.startip = values.startip
+        params.endip = values.endip
+        params.ip6cidr = values.ip6cidr
+        params.ip6gateway = values.ip6gateway
+        params.startipv6 = values.startipv6
+        params.endipv6 = values.endipv6
+
+        this.loading = true
+
+        api('createVlanIpRange', params)
+          .then(() => {
+            this.$notification.success({
+              message: this.$t('message.success.add.iprange')
+            })
+            this.closeAction()
+            this.$emit('refresh-data')
+          }).catch(error => {
+            this.$notification.error({
+              message: `${this.$t('label.error')} ${error.response.status}`,
+              description: error.response.data.createvlaniprangeresponse
+                ? error.response.data.createvlaniprangeresponse.errortext : error.response.data.errorresponse.errortext,
+              duration: 0
+            })
+          }).finally(() => {
+            this.loading = false
+          })
+      })
+    },
+    closeAction () {
+      this.$emit('close-action')
+    },
+    checkIpFormat (rule, value, callback) {
+      if (!value || value === '') {
+        callback()
+      } else if (rule.ipV4 && !this.ipV4Regex.test(value)) {
+        callback(rule.message)
+      } else if (rule.ipV6 && !this.ipV6Regex.test(value)) {
+        callback(rule.message)
+      } else {
+        callback()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.form-layout {
+  width: 60vw;
+
+  @media (min-width: 500px) {
+    width: 450px;
+  }
+}
+
+.action-button {
+  text-align: right;
+
+  button {
+    margin-right: 5px;
+  }
+}
+</style>
diff --git a/ui/src/views/network/GuestIpRanges.vue b/ui/src/views/network/GuestIpRanges.vue
new file mode 100644
index 0000000..a2e364a
--- /dev/null
+++ b/ui/src/views/network/GuestIpRanges.vue
@@ -0,0 +1,196 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+  <div>
+    <a-spin :spinning="fetchLoading">
+      <a-button
+        icon="plus"
+        shape="round"
+        style="float: right;margin-bottom: 10px; z-index: 8"
+        @click="() => { showCreateForm = true }">
+        {{ $t('label.add.ip.range') }}
+      </a-button>
+      <br />
+      <br />
+
+      <a-table
+        size="small"
+        style="overflow-y: auto; width: 100%;"
+        :columns="columns"
+        :dataSource="ipranges"
+        :rowKey="item => item.id"
+        :pagination="false" >
+
+        <template slot="action" slot-scope="text, record">
+          <a-tooltip placement="bottom">
+            <template slot="title">
+              {{ $t('label.action.delete.ip.range') }}
+            </template>
+            <a-popconfirm
+              :title="$t('message.confirm.remove.ip.range')"
+              @confirm="removeIpRange(record.id)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')" >
+              <a-button
+                type="danger"
+                icon="delete"
+                shape="circle" />
+            </a-popconfirm>
+          </a-tooltip>
+        </template>
+
+      </a-table>
+      <a-divider/>
+      <a-pagination
+        class="row-element pagination"
+        size="small"
+        :current="page"
+        :pageSize="pageSize"
+        :total="total"
+        :showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`"
+        :pageSizeOptions="['10', '20', '40', '80', '100']"
+        @change="changePage"
+        @showSizeChange="changePageSize"
+        showSizeChanger>
+        <template slot="buildOptionText" slot-scope="props">
+          <span>{{ props.value }} / {{ $t('label.page') }}</span>
+        </template>
+      </a-pagination>
+    </a-spin>
+    <a-modal
+      v-if="showCreateForm"
+      :visible="showCreateForm"
+      :title="$t('label.add.ip.range')"
+      :maskClosable="false"
+      :footer="null"
+      :cancelText="$t('label.cancel')"
+      @cancel="() => { showCreateForm = false }"
+      centered
+      width="auto">
+      <CreateVlanIpRange
+        :resource="resource"
+        @refresh-data="fetchData"
+        @close-action="showCreateForm = false" />
+    </a-modal>
+  </div>
+</template>
+<script>
+import { api } from '@/api'
+import CreateVlanIpRange from '@/views/network/CreateVlanIpRange'
+export default {
+  name: 'GuestIpRanges',
+  components: {
+    CreateVlanIpRange
+  },
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      fetchLoading: false,
+      showCreateForm: false,
+      total: 0,
+      ipranges: [],
+      page: 1,
+      pageSize: 10,
+      columns: [
+        {
+          title: this.$t('label.startipv4'),
+          dataIndex: 'startip'
+        },
+        {
+          title: this.$t('label.endipv4'),
+          dataIndex: 'endip'
+        },
+        {
+          title: this.$t('label.startipv6'),
+          dataIndex: 'startipv6'
+        },
+        {
+          title: this.$t('label.endipv6'),
+          dataIndex: 'endipv6'
+        },
+        {
+          title: this.$t('label.gateway'),
+          dataIndex: 'gateway'
+        },
+        {
+          title: this.$t('label.netmask'),
+          dataIndex: 'netmask'
+        },
+        {
+          title: '',
+          scopedSlots: { customRender: 'action' }
+        }
+      ]
+    }
+  },
+  mounted () {
+    this.fetchData()
+  },
+  watch: {
+    resource: function (newItem, oldItem) {
+      if (!newItem || !newItem.id) {
+        return
+      }
+      this.fetchData()
+    }
+  },
+  methods: {
+    fetchData () {
+      const params = {
+        zoneid: this.resource.zoneid,
+        networkid: this.resource.id,
+        listall: true,
+        page: this.page,
+        pagesize: this.pageSize
+      }
+      this.fetchLoading = true
+      api('listVlanIpRanges', params).then(json => {
+        this.total = json.listvlaniprangesresponse.count || 0
+        this.ipranges = json.listvlaniprangesresponse.vlaniprange || []
+      }).finally(() => {
+        this.fetchLoading = false
+      })
+    },
+    removeIpRange (id) {
+      api('deleteVlanIpRange', { id: id }).then(json => {
+      }).finally(() => {
+        this.fetchData()
+      })
+    },
+    changePage (page, pageSize) {
+      this.page = page
+      this.pageSize = pageSize
+      this.fetchData()
+    },
+    changePageSize (currentPage, pageSize) {
+      this.page = currentPage
+      this.pageSize = pageSize
+      this.fetchData()
+    }
+  }
+}
+</script>