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>