network: IP Address and Router Tabs (#152)

Guest network - IP Address and Router Tabs

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
Co-authored-by: Rohit Yadav <rohit@apache.org>
This commit is contained in:
Pearl Dsilva 2020-02-12 15:15:07 +05:30 committed by Rohit Yadav
parent 4cd63a763e
commit ae50e11888
5 changed files with 451 additions and 237 deletions

View File

@ -15,6 +15,8 @@
// specific language governing permissions and limitations
// under the License.
import store from '@/store'
export default {
name: 'network',
title: 'Network',
@ -29,14 +31,6 @@ export default {
columns: ['name', 'state', 'type', 'cidr', 'ip6cidr', 'broadcasturi', 'account', 'zonename'],
details: ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'zonename', 'account', 'domain'],
related: [{
name: 'publicip',
title: 'IP Addresses',
param: 'associatednetworkid'
}, {
name: 'router',
title: 'Routers',
param: 'networkid'
}, {
name: 'vm',
title: 'Instances',
param: 'networkid'
@ -47,7 +41,15 @@ export default {
}, {
name: 'Egress Rules',
component: () => import('@/views/network/EgressConfigure.vue'),
show: () => true
show: (record) => { return record.type === 'Isolated' && 'listEgressFirewallRules' in store.getters.apis }
}, {
name: 'Public IP Addresses',
component: () => import('@/views/network/IpAddressesTab.vue'),
show: (record) => { return record.type === 'Isolated' && 'listPublicIpAddresses' in store.getters.apis }
}, {
name: 'Virtual Routers',
component: () => import('@/views/network/RoutersTab.vue'),
show: (record) => { return (record.type === 'Isolated' || record.type === 'Shared') && 'listRouters' in store.getters.apis }
}],
actions: [
{

View File

@ -2,6 +2,7 @@
"Accounts": "Accounts",
"Affinity Groups": "Affinity Groups",
"Alerts": "Alerts",
"allocated": "Allocated",
"cancel": "Cancel",
"CPU Sockets": "CPU Sockets",
"Cloudian Storage": "Cloudian Storage",

View File

@ -0,0 +1,232 @@
// 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="fetchLoading">
<a-button type="dashed" icon="plus" style="width: 100%; margin-bottom: 15px" @click="acquireIpAddress">
{{ $t("label.acquire.new.ip") }}
</a-button>
<a-table
size="small"
style="overflow-y: auto"
:columns="columns"
:dataSource="ips"
:rowKey="item => item.id"
:pagination="false" >
<template slot="ipaddress" slot-scope="text, record">
<router-link :to="{ path: '/publicip/' + record.id }" >{{ text }} </router-link>
<a-tag v-if="record.issourcenat === true">source-nat</a-tag>
</template>
<template slot="state" slot-scope="text, record">
<status :text="record.state" displayText />
</template>
<template slot="virtualmachineid" slot-scope="text, record">
<a-icon type="desktop" v-if="record.virtualmachineid" />
<router-link :to="{ path: '/vm/' + record.virtualmachineid }" > {{ record.virtualmachinename || record.virtualmachineid }} </router-link>
</template>
<template slot="action" slot-scope="text, record">
<a-button
v-if="record.issourcenat !== true"
type="danger"
icon="delete"
shape="circle"
@click="releaseIpAddress(record)" />
</template>
</a-table>
<a-divider/>
<a-pagination
class="row-element pagination"
size="small"
:current="page"
:pageSize="pageSize"
:total="totalIps"
:showTotal="total => `Total ${total} items`"
:pageSizeOptions="['10', '20', '40', '80', '100']"
@change="changePage"
@showSizeChange="changePageSize"
showSizeChanger/>
</a-spin>
</template>
<script>
import { api } from '@/api'
import Status from '@/components/widgets/Status'
export default {
name: 'IpAddressesTab',
components: {
Status
},
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
default: false
}
},
data () {
return {
totalIps: 0,
page: 1,
pageSize: 10,
columns: [
{
title: this.$t('ipaddress'),
dataIndex: 'ipaddress',
scopedSlots: { customRender: 'ipaddress' }
},
{
title: this.$t('state'),
dataIndex: 'state',
scopedSlots: { customRender: 'state' }
},
{
title: this.$t('vm'),
dataIndex: 'virtualmachineid',
scopedSlots: { customRender: 'virtualmachineid' }
},
{
title: this.$t('Network'),
dataIndex: 'associatednetworkname'
},
{
title: '',
scopedSlots: { customRender: 'action' }
}
],
fetchLoading: false,
ips: [],
regions: [],
clicked: '',
action: {
acquire: 'Please confirm that you want to acquire new IP',
release: 'Please confirm that you want to release this IP'
},
visible: false
}
},
mounted () {
this.fetchData()
},
watch: {
resource: function (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
}
this.fetchData()
}
},
methods: {
fetchData () {
const params = {
listall: true,
page: this.page,
pagesize: this.pageSize
}
if (this.$route.path.startsWith('/vpc')) {
params.vpcid = this.resource.id
params.forvirtualnetwork = true
} else {
params.associatednetworkid = this.resource.id
}
this.fetchLoading = true
api('listPublicIpAddresses', params).then(json => {
this.totalIps = json.listpublicipaddressesresponse.count || 0
this.ips = json.listpublicipaddressesresponse.publicipaddress || []
}).finally(() => {
this.fetchLoading = false
})
},
changePage (page, pageSize) {
this.page = page
this.pageSize = pageSize
this.fetchData()
},
changePageSize (currentPage, pageSize) {
this.page = currentPage
this.pageSize = pageSize
this.fetchData()
},
acquireIpAddress () {
const params = {}
if (this.$route.path.startsWith('/vpc')) {
params.vpcid = this.resource.id
} else {
params.networkid = this.resource.id
}
this.fetchLoading = true
api('associateIpAddress', params).then(response => {
this.$pollJob({
jobId: response.associateipaddressresponse.jobid,
successMessage: `Successfully acquired IP for ${this.resource.name}`,
successMethod: () => {
this.fetchData()
},
errorMessage: 'Failed to acquire IP',
errorMethod: () => {
this.fetchData()
},
loadingMessage: `Acquiring IP for ${this.resource.name} is in progress`,
catchMessage: 'Error encountered while fetching async job result'
})
}).catch(error => {
this.fetchLoading = false
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0
})
})
},
releaseIpAddress (ip) {
this.fetchLoading = true
api('disassociateIpAddress', {
id: ip.id
}).then(response => {
this.$pollJob({
jobId: response.disassociateipaddressresponse.jobid,
successMessage: 'Successfully released IP',
successMethod: () => {
this.fetchData()
},
errorMessage: 'Failed to release IP',
errorMethod: () => {
this.fetchData()
},
loadingMessage: `Releasing IP for ${this.resource.name} is in progress`,
catchMessage: 'Error encountered while fetching async job result'
})
}).catch(error => {
this.fetchLoading = false
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0
})
})
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,151 @@
// 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-table
size="small"
style="overflow-y: auto"
:columns="columns"
:dataSource="routers"
:rowKey="item => item.id"
:pagination="false"
:loading="fetchLoading"
>
<template slot="name" slot-scope="text,item">
<router-link :to="{ path: '/router/' + item.id }" >{{ text }}</router-link>
</template>
<template slot="status" slot-scope="text, item">
<status class="status" :text="item.state" displayText />
</template>
<template slot="requiresupgrade" slot-scope="text, item">
{{ item.requiresupgrade ? $t('Yes') : $t('No') }}
</template>
<template slot="isredundantrouter" slot-scope="text, record">
{{ record.isredundantrouter ? record.redundantstate : record.isredundantrouter }}
</template>
<template slot="hostname" slot-scope="text, record">
<router-link :to="{ path: '/host/' + record.hostid }" >{{ record.hostname || record.hostid }}</router-link>
</template>
</a-table>
</template>
<script>
import { api } from '@/api'
import Status from '@/components/widgets/Status'
export default {
name: 'RoutersTab',
components: {
Status
},
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
default: false
}
},
data () {
return {
fetchLoading: false,
routers: [],
columns: [
{
title: this.$t('name'),
dataIndex: 'name',
scopedSlots: { customRender: 'name' }
},
{
title: this.$t('status'),
dataIndex: 'state',
scopedSlots: { customRender: 'status' }
},
{
title: this.$t('ip'),
dataIndex: 'publicip'
},
{
title: this.$t('version'),
dataIndex: 'version'
},
{
title: this.$t('requiresupgrade'),
dataIndex: 'requiresupgrade',
scopedSlots: { customRender: 'requiresupgrade' }
},
{
title: this.$t('isredundantrouter'),
dataIndex: 'isredundantrouter',
scopedSlots: { customRender: 'isredundantrouter' }
},
{
title: this.$t('hostname'),
dataIndex: 'hostname',
scopedSlots: { customRender: 'hostname' }
}
]
}
},
mounted () {
this.fetchData()
},
watch: {
resource: function (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
}
this.fetchData()
}
},
methods: {
fetchData () {
var params = {
listAll: true
}
if (this.$route.fullPath.startsWith('/vpc')) {
params.vpcid = this.resource.id
} else {
params.networkid = this.resource.id
}
this.fetchLoading = true
api('listRouters', params).then(json => {
this.routers = json.listroutersresponse.router || []
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: error.response.headers['x-description'],
duration: 0
})
}).finally(() => {
this.fetchLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.status {
margin-top: -5px;
&--end {
margin-left: 5px;
}
}
</style>

View File

@ -28,6 +28,56 @@
<a-tab-pane :tab="$t('networks')" key="tier">
<VpcTiersTab :resource="resource" :loading="loading" />
</a-tab-pane>
<a-tab-pane tab="Public IP Addresses" key="ip" v-if="'listPublicIpAddresses' in $store.getters.apis">
<IpAddressesTab :resource="resource" :loading="loading" />
</a-tab-pane>
<a-tab-pane tab="Network ACL Lists" key="acl" v-if="'listNetworkACLLists' in $store.getters.apis">
<a-button
type="dashed"
icon="plus"
style="width: 100%"
@click="() => handleOpenModals('networkAcl')">
Add Network ACL List
</a-button>
<a-table
class="table"
size="small"
:columns="networkAclsColumns"
:dataSource="networkAcls"
:rowKey="item => item.id"
:pagination="false"
>
<template slot="name" slot-scope="text, item">
<router-link :to="{ path: '/acllist/' + item.id }">
{{ text }}
</router-link>
</template>
</a-table>
<a-pagination
class="row-element pagination"
size="small"
:current="page"
:pageSize="pageSize"
:total="itemCounts.networkAcls"
:showTotal="total => `Total ${total} items`"
:pageSizeOptions="['10', '20', '40', '80', '100']"
@change="changePage"
@showSizeChange="changePageSize"
showSizeChanger/>
<a-modal
v-model="modals.networkAcl"
:title="$t('label.add.acl.list')"
@ok="handleNetworkAclFormSubmit">
<a-form @submit.prevent="handleNetworkAclFormSubmit" :form="networkAclForm">
<a-form-item :label="$t('label.add.list.name')">
<a-input v-decorator="['name', {rules: [{ required: true, message: 'Required' }]}]"></a-input>
</a-form-item>
<a-form-item :label="$t('description')">
<a-input v-decorator="['description', {rules: [{ required: true, message: 'Required' }]}]"></a-input>
</a-form-item>
</a-form>
</a-modal>
</a-tab-pane>
<a-tab-pane tab="Private Gateways" key="pgw" v-if="'listPrivateGateways' in $store.getters.apis">
<a-button
type="dashed"
@ -110,49 +160,6 @@
</a-spin>
</a-modal>
</a-tab-pane>
<a-tab-pane tab="Public IP Addresses" key="ip" v-if="'listPublicIpAddresses' in $store.getters.apis">
<a-button type="dashed" icon="plus" style="width: 100%" @click="handleAcquireNewIp">Acquire New IP</a-button>
<a-table
class="table"
size="small"
:columns="publicIpAddressesColumns"
:dataSource="publicIpAddresses"
:rowKey="item => item.id"
:pagination="false"
>
<template slot="ipaddress" slot-scope="text, item">
<router-link :to="{ path: '/publicip/' + item.id }">
{{ text }}
<a-tag v-if="item.issourcenat">source-nat</a-tag>
<a-tag v-if="item.isstaticnat">static-nat</a-tag>
</router-link>
</template>
<template slot="state" slot-scope="text, item">
<status :text="item.state" displayText></status>
</template>
<template slot="vm" slot-scope="text, item">
<router-link :to="{ path: '/vm/' + item.virtualmachineid }">
{{ item.virtualmachinedisplayname || item.virtualmachinename }}
</router-link>
</template>
<template slot="network" slot-scope="text, item">
<router-link :to="{ path: '/guestnetwork/' + item.associatednetworkid }">
{{ item.associatednetworkname }}
</router-link>
</template>
</a-table>
<a-pagination
class="row-element pagination"
size="small"
:current="page"
:pageSize="pageSize"
:total="itemCounts.publicIpAddresses"
:showTotal="total => `Total ${total} items`"
:pageSizeOptions="['10', '20', '40', '80', '100']"
@change="changePage"
@showSizeChange="changePageSize"
showSizeChanger/>
</a-tab-pane>
<a-tab-pane tab="VPN Gateway" key="vpngw" v-if="'listVpnGateways' in $store.getters.apis">
<a-button
v-if="vpnGateways.length === 0"
@ -229,94 +236,8 @@
</a-spin>
</a-modal>
</a-tab-pane>
<a-tab-pane tab="Network ACL Lists" key="acl" v-if="'listNetworkACLLists' in $store.getters.apis">
<a-button
type="dashed"
icon="plus"
style="width: 100%"
@click="() => handleOpenModals('networkAcl')">
Add Network ACL List
</a-button>
<a-table
class="table"
size="small"
:columns="networkAclsColumns"
:dataSource="networkAcls"
:rowKey="item => item.id"
:pagination="false"
>
<template slot="name" slot-scope="text, item">
<router-link :to="{ path: '/acllist/' + item.id }">
{{ text }}
</router-link>
</template>
</a-table>
<a-pagination
class="row-element pagination"
size="small"
:current="page"
:pageSize="pageSize"
:total="itemCounts.networkAcls"
:showTotal="total => `Total ${total} items`"
:pageSizeOptions="['10', '20', '40', '80', '100']"
@change="changePage"
@showSizeChange="changePageSize"
showSizeChanger/>
<a-modal
v-model="modals.networkAcl"
:title="$t('label.add.acl.list')"
@ok="handleNetworkAclFormSubmit">
<a-form @submit.prevent="handleNetworkAclFormSubmit" :form="networkAclForm">
<a-form-item :label="$t('label.add.list.name')">
<a-input v-decorator="['name', {rules: [{ required: true, message: 'Required' }]}]"></a-input>
</a-form-item>
<a-form-item :label="$t('description')">
<a-input v-decorator="['description', {rules: [{ required: true, message: 'Required' }]}]"></a-input>
</a-form-item>
</a-form>
</a-modal>
</a-tab-pane>
<a-tab-pane tab="Virtual Routers" key="vr" v-if="'listRouters' in $store.getters.apis">
<a-list>
<a-list-item v-for="item in routers" :key="item.id">
<div class="list__item">
<div class="list__row">
<div class="list__col">
<div class="list__label">{{ $t('name') }}</div>
<div>
<router-link :to="{ path: '/router/' + item.id }">
{{ item.name }}
</router-link>
</div>
</div>
<div class="list__col">
<div class="list__label">{{ $t('state') }}</div>
<div><status :text="item.state" displayText></status></div>
</div>
<div class="list__col">
<div class="list__label">{{ $t('publicip') }}</div>
<div>{{ item.publicip }}</div>
</div>
<div class="list__col">
<div class="list__label">{{ $t('redundantrouter') }}</div>
<div>{{ item.isredundantrouter }}</div>
</div>
<div class="list__col">
<div class="list__label">{{ $t('redundantstate') }}</div>
<div>{{ item.redundantstate }}</div>
</div>
<div class="list__col">
<div class="list__label">{{ $t('hostname') }}</div>
<div>
<router-link :to="{ path: '/host/' + item.hostid }">
{{ item.hostname }}
</router-link>
</div>
</div>
</div>
</div>
</a-list-item>
</a-list>
<RoutersTab :resource="resource" :loading="loading" />
</a-tab-pane>
</a-tabs>
</a-spin>
@ -324,16 +245,20 @@
<script>
import { api } from '@/api'
import { mixinDevice } from '@/utils/mixin.js'
import DetailsTab from '@/components/view/DetailsTab'
import Status from '@/components/widgets/Status'
import IpAddressesTab from './IpAddressesTab'
import RoutersTab from './RoutersTab'
import VpcTiersTab from './VpcTiersTab'
import { mixinDevice } from '@/utils/mixin.js'
export default {
name: 'VpcTab',
components: {
DetailsTab,
Status,
IpAddressesTab,
RoutersTab,
VpcTiersTab
},
mixins: [mixinDevice],
@ -349,10 +274,8 @@ export default {
},
data () {
return {
routers: [],
fetchLoading: false,
privateGateways: [],
publicIpAddresses: [],
vpnGateways: [],
vpnConnections: [],
networkAcls: [],
@ -415,28 +338,6 @@ export default {
dataIndex: 'ipsecpsk'
}
],
publicIpAddressesColumns: [
{
title: this.$t('ip'),
dataIndex: 'ipaddress',
scopedSlots: { customRender: 'ipaddress' }
},
{
title: this.$t('state'),
dataIndex: 'state',
scopedSlots: { customRender: 'state' }
},
{
title: this.$t('vm'),
dataIndex: 'vm',
scopedSlots: { customRender: 'vm' }
},
{
title: this.$t('network'),
dataIndex: 'network',
scopedSlots: { customRender: 'network' }
}
],
networkAclsColumns: [
{
title: this.$t('name'),
@ -450,7 +351,6 @@ export default {
],
itemCounts: {
privateGateways: 0,
publicIpAddresses: 0,
vpnConnections: 0,
networkAcls: 0
},
@ -486,9 +386,6 @@ export default {
case 'pgw':
this.fetchPrivateGateways()
break
case 'ip':
this.fetchPublicIpAddresses()
break
case 'vpngw':
this.fetchVpnGateways()
break
@ -498,24 +395,8 @@ export default {
case 'acl':
this.fetchAclList()
break
case 'vr':
this.fetchRouters()
break
}
},
fetchRouters () {
this.fetchLoading = true
api('listRouters', { vpcid: this.resource.id, listAll: true }).then(json => {
this.routers = json.listroutersresponse.router
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: error.response.headers['x-description']
})
}).finally(() => {
this.fetchLoading = false
})
},
fetchPrivateGateways () {
this.fetchLoading = true
api('listPrivateGateways', {
@ -535,26 +416,6 @@ export default {
this.fetchLoading = false
})
},
fetchPublicIpAddresses () {
this.fetchLoading = true
api('listPublicIpAddresses', {
vpcid: this.resource.id,
listAll: true,
page: this.page,
pagesize: this.pageSize,
forvirtualnetwork: true
}).then(json => {
this.publicIpAddresses = json.listpublicipaddressesresponse.publicipaddress
this.itemCounts.publicIpAddresses = json.listpublicipaddressesresponse.count
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: error.response.headers['x-description']
})
}).finally(() => {
this.fetchLoading = false
})
},
fetchVpnGateways () {
this.fetchLoading = true
api('listVpnGateways', {
@ -726,39 +587,6 @@ export default {
})
})
},
handleAcquireNewIp () {
this.fetchLoading = true
api('associateIpAddress', { vpcid: this.resource.id }).then(response => {
this.$store.dispatch('AddAsyncJob', {
title: `Successfully acquired new IP`,
jobid: response.associateipaddressresponse.jobid,
status: 'progress'
})
this.$pollJob({
jobId: response.associateipaddressresponse.jobid,
successMethod: () => {
this.fetchPublicIpAddresses()
},
errorMessage: 'Failed to acquire new IP',
errorMethod: () => {
this.fetchPublicIpAddresses()
},
loadingMessage: `Acquiring new IP...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.fetchPublicIpAddresses()
}
})
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: error.response.headers['x-description']
})
}).finally(() => {
this.fetchLoading = false
this.fetchPublicIpAddresses()
})
},
handleVpnConnectionFormSubmit () {
this.fetchLoading = true
this.modals.vpnConnection = false