mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
infra: zone and physical network, ip ranges tabs for traffic types (#134)
Physical network and systemvms tabs for zone. IP ranges tabs for traffic type management. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com> Co-authored-by: Rohit Yadav <rohit@apache.org>
This commit is contained in:
parent
c599b2fc11
commit
edeb25dfbc
@ -23,10 +23,6 @@ export default {
|
||||
columns: ['name', 'state', 'networktype', 'clusters', 'cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal', 'order'],
|
||||
details: ['name', 'id', 'allocationstate', 'networktype', 'guestcidraddress', 'localstorageenabled', 'securitygroupsenabled', 'dns1', 'dns2', 'internaldns1', 'internaldns2'],
|
||||
related: [{
|
||||
name: 'physicalnetwork',
|
||||
title: 'Physical Networks',
|
||||
param: 'zoneid'
|
||||
}, {
|
||||
name: 'pod',
|
||||
title: 'Pods',
|
||||
param: 'zoneid'
|
||||
@ -38,10 +34,6 @@ export default {
|
||||
name: 'host',
|
||||
title: 'Hosts',
|
||||
param: 'zoneid'
|
||||
}, {
|
||||
name: 'systemvm',
|
||||
title: 'SystemVMs',
|
||||
param: 'zoneid'
|
||||
}, {
|
||||
name: 'storagepool',
|
||||
title: 'Primate Storage',
|
||||
@ -54,9 +46,15 @@ export default {
|
||||
tabs: [{
|
||||
name: 'details',
|
||||
component: () => import('@/components/view/DetailsTab.vue')
|
||||
}, {
|
||||
name: 'Physical Networks',
|
||||
component: () => import('@/views/infra/zone/PhysicalNetworksTab.vue')
|
||||
}, {
|
||||
name: 'System VMs',
|
||||
component: () => import('@/views/infra/zone/SystemVmsTab.vue')
|
||||
}, {
|
||||
name: 'resources',
|
||||
component: () => import('@/views/infra/ZoneResources.vue')
|
||||
component: () => import('@/views/infra/zone/ZoneResources.vue')
|
||||
}, {
|
||||
name: 'settings',
|
||||
component: () => import('@/components/view/SettingsTab.vue')
|
||||
@ -68,7 +66,7 @@ export default {
|
||||
label: 'Add Zone',
|
||||
listView: true,
|
||||
popup: true,
|
||||
component: () => import('@/views/infra/ZoneWizard.vue')
|
||||
component: () => import('@/views/infra/zone/ZoneWizard.vue')
|
||||
},
|
||||
{
|
||||
api: 'updateZone',
|
||||
|
||||
@ -611,6 +611,7 @@
|
||||
"label.recover.vm": "Recover VM",
|
||||
"label.refresh.blades": "Refresh Blades",
|
||||
"label.reinstall.vm": "Reinstall VM",
|
||||
"label.release.account": "Release from Account",
|
||||
"label.release.dedicated.cluster": "Release Dedicated Cluster",
|
||||
"label.release.dedicated.host": "Release Dedicated Host",
|
||||
"label.release.dedicated.pod": "Release Dedicated Pod",
|
||||
@ -639,6 +640,7 @@
|
||||
"label.secondary.storage.vm":"Secondary storage VM",
|
||||
"label.service.offering":"Service Offering",
|
||||
"label.set.default.NIC": "Set default NIC",
|
||||
"label.set.reservation": "Set Reservation",
|
||||
"label.shutdown.provider": "Shutdown provider",
|
||||
"label.snapshot.schedule": "Set up Recurring Snapshot",
|
||||
"label.standard.us.keyboard": "Standard (US) keyboard",
|
||||
@ -1011,7 +1013,7 @@
|
||||
"vmdisplayname": "VM display name",
|
||||
"vmipaddress": "VM IP Address",
|
||||
"vmname": "VM Name",
|
||||
"vmstate": "VM state",
|
||||
"vmstate": "VM State",
|
||||
"vmtotal": "Total of VMs",
|
||||
"vmwaredcId": "VMware Datacenter ID",
|
||||
"vmwaredcName": "VMware Datacenter Name",
|
||||
|
||||
413
ui/src/views/infra/network/IpRangesTabManagement.vue
Normal file
413
ui/src/views/infra/network/IpRangesTabManagement.vue
Normal file
@ -0,0 +1,413 @@
|
||||
// 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="componentLoading">
|
||||
<a-button
|
||||
type="dashed"
|
||||
icon="plus"
|
||||
style="margin-bottom: 20px; width: 100%"
|
||||
@click="handleOpenAddIpRangeModal">
|
||||
{{ $t('label.add.ip.range') }}
|
||||
</a-button>
|
||||
|
||||
<a-table
|
||||
style="overflow-y: auto"
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:dataSource="items"
|
||||
:rowKey="record => record.id + record.startip"
|
||||
:pagination="false"
|
||||
>
|
||||
<template slot="forsystemvms" slot-scope="text, record">
|
||||
<a-checkbox :checked="record.forsystemvms" />
|
||||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<div class="actions">
|
||||
<a-popover placement="bottom">
|
||||
<template slot="content">{{ $t('label.remove.ip.range') }}</template>
|
||||
<a-button
|
||||
icon="delete"
|
||||
shape="round"
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="handleDeleteIpRange(record)"></a-button>
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
class="row-element pagination"
|
||||
size="small"
|
||||
style="overflow-y: auto"
|
||||
:current="page"
|
||||
:pageSize="pageSize"
|
||||
:total="items.length"
|
||||
:showTotal="total => `Total ${total} items`"
|
||||
:pageSizeOptions="['10', '20', '40', '80', '100']"
|
||||
@change="changePage"
|
||||
@showSizeChange="changePageSize"
|
||||
showSizeChanger/>
|
||||
|
||||
<a-modal v-model="addIpRangeModal" :title="$t('label.add.ip.range')" @ok="handleAddIpRange">
|
||||
<a-form
|
||||
:form="form"
|
||||
@submit="handleAddIpRange"
|
||||
layout="vertical"
|
||||
class="form"
|
||||
>
|
||||
<a-form-item :label="$t('podId')" class="form__item">
|
||||
<a-select
|
||||
v-decorator="['pod', {
|
||||
rules: [{ required: true, message: 'Required' }]
|
||||
}]"
|
||||
>
|
||||
<a-select-option v-for="item in items" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('gateway')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['gateway', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('netmask')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['netmask', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('vlan')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['vlan']">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('startip')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['startip', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('endip')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['endip', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('System VMs')" class="form__item">
|
||||
<a-checkbox v-decorator="['vms']"></a-checkbox>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'IpRangesTabManagement',
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
componentLoading: false,
|
||||
items: [],
|
||||
domains: [],
|
||||
domainsLoading: false,
|
||||
addIpRangeModal: false,
|
||||
defaultSelectedPod: null,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
columns: [
|
||||
{
|
||||
title: this.$t('podid'),
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: this.$t('gateway'),
|
||||
dataIndex: 'gateway'
|
||||
},
|
||||
{
|
||||
title: this.$t('netmask'),
|
||||
dataIndex: 'netmask'
|
||||
},
|
||||
{
|
||||
title: this.$t('vlan'),
|
||||
dataIndex: 'vlanid',
|
||||
scopedSlots: { customRender: 'vlan' }
|
||||
},
|
||||
{
|
||||
title: this.$t('startip'),
|
||||
dataIndex: 'startip',
|
||||
scopedSlots: { customRender: 'startip' }
|
||||
},
|
||||
{
|
||||
title: this.$t('endip'),
|
||||
dataIndex: 'endip',
|
||||
scopedSlots: { customRender: 'endip' }
|
||||
},
|
||||
{
|
||||
title: this.$t('System VMs'),
|
||||
dataIndex: 'forsystemvms',
|
||||
scopedSlots: { customRender: 'forsystemvms' }
|
||||
},
|
||||
{
|
||||
title: this.$t('action'),
|
||||
scopedSlots: { customRender: 'actions' }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
mounted () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
resource (newItem, oldItem) {
|
||||
if (!newItem || !newItem.id) {
|
||||
return
|
||||
}
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.componentLoading = true
|
||||
api('listPods', {
|
||||
zoneid: this.resource.zoneid,
|
||||
page: this.page,
|
||||
pagesize: this.pageSize
|
||||
}).then(response => {
|
||||
this.items = []
|
||||
const pods = response.listpodsresponse.pod ? response.listpodsresponse.pod : []
|
||||
for (const pod of pods) {
|
||||
if (pod && pod.startip && pod.startip.length > 0) {
|
||||
for (var idx = 0; idx < pod.startip.length; idx++) {
|
||||
this.items.push({
|
||||
id: pod.id,
|
||||
name: pod.name,
|
||||
gateway: pod.gateway,
|
||||
netmask: pod.netmask,
|
||||
vlanid: pod.vlanid[idx],
|
||||
startip: pod.startip[idx],
|
||||
endip: pod.endip[idx],
|
||||
forsystemvms: pod.forsystemvms[idx] === '1'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.listpodsresponse
|
||||
? error.response.data.listpodsresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.componentLoading = false
|
||||
})
|
||||
},
|
||||
handleOpenAddIpRangeModal () {
|
||||
this.addIpRangeModal = true
|
||||
setTimeout(() => {
|
||||
if (this.items.length > 0) {
|
||||
this.form.setFieldsValue({
|
||||
pod: this.items[0].id
|
||||
})
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
handleDeleteIpRange (record) {
|
||||
this.componentLoading = true
|
||||
api('deleteManagementNetworkIpRange', {
|
||||
podid: record.id,
|
||||
startip: record.startip,
|
||||
endip: record.endip,
|
||||
vlan: record.vlanid
|
||||
}).then(response => {
|
||||
this.$store.dispatch('AddAsyncJob', {
|
||||
title: `Successfully removed IP Range`,
|
||||
jobid: response.deletemanagementnetworkiprangeresponse.jobid,
|
||||
status: 'progress'
|
||||
})
|
||||
this.$pollJob({
|
||||
jobId: response.deletemanagementnetworkiprangeresponse.jobid,
|
||||
successMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
},
|
||||
errorMessage: 'Removing failed',
|
||||
errorMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
},
|
||||
loadingMessage: `Removing IP Range...`,
|
||||
catchMessage: 'Error encountered while fetching async job result',
|
||||
catchMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
}
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.deletemanagementnetworkiprangeresponse
|
||||
? error.response.data.deletemanagementnetworkiprangeresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
})
|
||||
},
|
||||
handleAddIpRange (e) {
|
||||
this.form.validateFields((error, values) => {
|
||||
if (error) return
|
||||
|
||||
this.componentLoading = true
|
||||
this.addIpRangeModal = false
|
||||
api('createManagementNetworkIpRange', {
|
||||
podid: values.pod,
|
||||
gateway: values.gateway,
|
||||
netmask: values.netmask,
|
||||
startip: values.startip,
|
||||
endip: values.endip,
|
||||
forsystemvms: values.vms,
|
||||
vlan: values.vlan || null
|
||||
}).then(response => {
|
||||
this.$store.dispatch('AddAsyncJob', {
|
||||
title: `Successfully added IP Range`,
|
||||
jobid: response.createmanagementnetworkiprangeresponse.jobid,
|
||||
status: 'progress'
|
||||
})
|
||||
this.$pollJob({
|
||||
jobId: response.createmanagementnetworkiprangeresponse.jobid,
|
||||
successMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
},
|
||||
errorMessage: 'Adding failed',
|
||||
errorMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
},
|
||||
loadingMessage: `Adding IP Range...`,
|
||||
catchMessage: 'Error encountered while fetching async job result',
|
||||
catchMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
}
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.createmanagementnetworkiprangeresponse
|
||||
? error.response.data.createmanagementnetworkiprangeresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.componentLoading = false
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
&__item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__data {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__col {
|
||||
flex-basis: calc((100% / 3) - 20px);
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
}
|
||||
}
|
||||
|
||||
.ant-list-item {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
button {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form {
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
&:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__item {
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
489
ui/src/views/infra/network/IpRangesTabPublic.vue
Normal file
489
ui/src/views/infra/network/IpRangesTabPublic.vue
Normal file
@ -0,0 +1,489 @@
|
||||
// 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="componentLoading">
|
||||
<a-button
|
||||
type="dashed"
|
||||
icon="plus"
|
||||
style="margin-bottom: 20px; width: 100%"
|
||||
@click="handleOpenAddIpRangeModal">
|
||||
{{ $t('label.add.ip.range') }}
|
||||
</a-button>
|
||||
|
||||
<a-table
|
||||
size="small"
|
||||
style="overflow-y: auto"
|
||||
:columns="columns"
|
||||
:dataSource="items"
|
||||
:rowKey="record => record.id"
|
||||
:pagination="false"
|
||||
>
|
||||
<template slot="account" slot-scope="record">
|
||||
<a-button @click="() => handleOpenAccountModal(record)">{{ `[${record.domain}] ${record.account}` }}</a-button>
|
||||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<div class="actions">
|
||||
<a-popover v-if="record.account === 'system'" placement="bottom">
|
||||
<template slot="content">{{ $t('label.add.account') }}</template>
|
||||
<a-button
|
||||
icon="user-add"
|
||||
shape="round"
|
||||
type="primary"
|
||||
@click="() => handleOpenAddAccountModal(record)"></a-button>
|
||||
</a-popover>
|
||||
<a-popover
|
||||
v-else
|
||||
placement="bottom">
|
||||
<template slot="content">{{ $t('label.release.account') }}</template>
|
||||
<a-button
|
||||
icon="user-delete"
|
||||
shape="round"
|
||||
type="danger"
|
||||
@click="() => handleRemoveAccount(record.id)"></a-button>
|
||||
</a-popover>
|
||||
<a-popover placement="bottom">
|
||||
<template slot="content">{{ $t('label.remove.ip.range') }}</template>
|
||||
<a-button icon="delete" shape="round" type="danger" @click="handleDeleteIpRange(record.id)"></a-button>
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
class="row-element pagination"
|
||||
size="small"
|
||||
style="overflow-y: auto"
|
||||
:current="page"
|
||||
:pageSize="pageSize"
|
||||
:total="items.length"
|
||||
:showTotal="total => `Total ${total} items`"
|
||||
:pageSizeOptions="['10', '20', '40', '80', '100']"
|
||||
@change="changePage"
|
||||
@showSizeChange="changePageSize"
|
||||
showSizeChanger/>
|
||||
|
||||
<a-modal v-model="accountModal" v-if="selectedItem" @ok="accountModal = false">
|
||||
<div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div class="list__label">{{ $t('account') }}</div>
|
||||
<div>{{ selectedItem.account }}</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div class="list__label">{{ $t('domain') }}</div>
|
||||
<div>{{ selectedItem.domain }}</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div class="list__label">{{ $t('System VMs') }}</div>
|
||||
<div>{{ selectedItem.forsystemvms }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<a-modal :zIndex="1001" v-model="addAccountModal" :title="$t('label.add.account')" @ok="handleAddAccount">
|
||||
<a-spin :spinning="domainsLoading">
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div class="list__label">{{ $t('account') }}:</div>
|
||||
<a-input v-model="addAccount.account"></a-input>
|
||||
</div>
|
||||
<div>
|
||||
<div class="list__label">{{ $t('domain') }}:</div>
|
||||
<a-select v-model="addAccount.domain">
|
||||
<a-select-option
|
||||
v-for="domain in domains"
|
||||
:key="domain.id"
|
||||
:value="domain.id">{{ domain.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
|
||||
<a-modal v-model="addIpRangeModal" :title="$t('label.add.ip.range')" @ok="handleAddIpRange">
|
||||
<a-form
|
||||
:form="form"
|
||||
@submit="handleAddIpRange"
|
||||
layout="vertical"
|
||||
class="form"
|
||||
>
|
||||
<a-form-item :label="$t('gateway')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['gateway', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('netmask')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['netmask', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('vlan')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['vlan']">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('startip')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['startip', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('endip')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['endip', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<div class="form__item">
|
||||
<div style="color: black;">{{ $t('label.set.reservation') }}</div>
|
||||
<a-switch @change="handleShowAccountFields"></a-switch>
|
||||
</div>
|
||||
<div v-if="showAccountFields" style="margin-top: 20px;">
|
||||
<p>(optional) Please specify an account to be associated with this IP range.</p>
|
||||
<p>System VMs: Enable dedication of public IP range for SSVM and CPVM, account field disabled. Reservation strictness defined on 'system.vm.public.ip.reservation.mode.strictness'.</p>
|
||||
<a-form-item :label="$t('System VMs')" class="form__item">
|
||||
<a-switch v-decorator="['forsystemvms']"></a-switch>
|
||||
</a-form-item>
|
||||
<a-spin :spinning="domainsLoading">
|
||||
<a-form-item :label="$t('account')" class="form__item">
|
||||
<a-input v-decorator="['account']"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('domain')" class="form__item">
|
||||
<a-select v-decorator="['domain']">
|
||||
<a-select-option
|
||||
v-for="domain in domains"
|
||||
:key="domain.id"
|
||||
:value="domain.id">{{ domain.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-spin>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'IpRangesTabPublic',
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
network: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
componentLoading: false,
|
||||
items: [],
|
||||
selectedItem: null,
|
||||
accountModal: false,
|
||||
addAccountModal: false,
|
||||
addAccount: {
|
||||
account: null,
|
||||
domain: null
|
||||
},
|
||||
domains: [],
|
||||
domainsLoading: false,
|
||||
addIpRangeModal: false,
|
||||
showAccountFields: false,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
columns: [
|
||||
{
|
||||
title: this.$t('gateway'),
|
||||
dataIndex: 'gateway'
|
||||
},
|
||||
{
|
||||
title: this.$t('netmask'),
|
||||
dataIndex: 'netmask'
|
||||
},
|
||||
{
|
||||
title: this.$t('vlan'),
|
||||
dataIndex: 'vlan'
|
||||
},
|
||||
{
|
||||
title: this.$t('startip'),
|
||||
dataIndex: 'startip'
|
||||
},
|
||||
{
|
||||
title: this.$t('endip'),
|
||||
dataIndex: 'endip'
|
||||
},
|
||||
{
|
||||
title: this.$t('account'),
|
||||
scopedSlots: { customRender: 'account' }
|
||||
},
|
||||
{
|
||||
title: this.$t('action'),
|
||||
scopedSlots: { customRender: 'actions' }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
mounted () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
network (newItem, oldItem) {
|
||||
if (!newItem || !newItem.id) {
|
||||
return
|
||||
}
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.componentLoading = true
|
||||
api('listVlanIpRanges', {
|
||||
networkid: this.network.id,
|
||||
zoneid: this.resource.zoneid,
|
||||
page: this.page,
|
||||
pagesize: this.pageSize
|
||||
}).then(response => {
|
||||
this.items = response.listvlaniprangesresponse.vlaniprange ? response.listvlaniprangesresponse.vlaniprange : []
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.listvlaniprangesresponse
|
||||
? error.response.data.listvlaniprangesresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.componentLoading = false
|
||||
})
|
||||
},
|
||||
fetchDomains () {
|
||||
this.domainsLoading = true
|
||||
api('listDomains', {
|
||||
details: 'min',
|
||||
listAll: true
|
||||
}).then(response => {
|
||||
this.domains = response.listdomainsresponse.domain ? response.listdomainsresponse.domain : []
|
||||
if (this.domains.length > 0) {
|
||||
this.addAccount.domain = this.domains[0].id
|
||||
this.form.setFieldsValue({ domain: this.domains[0].id })
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.listdomains
|
||||
? error.response.data.listdomains.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.domainsLoading = false
|
||||
})
|
||||
},
|
||||
handleAddAccount () {
|
||||
this.domainsLoading = true
|
||||
|
||||
if (this.addIpRangeModal === true) {
|
||||
this.addAccountModal = false
|
||||
return
|
||||
}
|
||||
|
||||
api('dedicatePublicIpRange', {
|
||||
id: this.selectedItem.id,
|
||||
zoneid: this.selectedItem.zoneid,
|
||||
domainid: this.addAccount.domain,
|
||||
account: this.addAccount.account
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.dedicatepubliciprangeresponse
|
||||
? error.response.data.dedicatepubliciprangeresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.addAccountModal = false
|
||||
this.domainsLoading = false
|
||||
this.fetchData()
|
||||
})
|
||||
},
|
||||
handleRemoveAccount (id) {
|
||||
this.componentLoading = true
|
||||
api('releasePublicIpRange', { id }).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.releasepubliciprangeresponse
|
||||
? error.response.data.releasepubliciprangeresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.fetchData()
|
||||
})
|
||||
},
|
||||
handleOpenAccountModal (item) {
|
||||
this.selectedItem = item
|
||||
this.accountModal = true
|
||||
},
|
||||
handleOpenAddAccountModal (item) {
|
||||
if (!this.addIpRangeModal) {
|
||||
this.selectedItem = item
|
||||
}
|
||||
this.addAccountModal = true
|
||||
this.fetchDomains()
|
||||
},
|
||||
handleShowAccountFields () {
|
||||
if (this.showAccountFields === false) {
|
||||
this.showAccountFields = true
|
||||
this.fetchDomains()
|
||||
return
|
||||
}
|
||||
this.showAccountFields = false
|
||||
},
|
||||
handleOpenAddIpRangeModal () {
|
||||
this.addIpRangeModal = true
|
||||
},
|
||||
handleDeleteIpRange (id) {
|
||||
this.componentLoading = true
|
||||
api('deleteVlanIpRange', { id }).then(() => {
|
||||
this.$notification.success({
|
||||
message: 'Removed IP Range'
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.deletevlaniprangeresponse
|
||||
? error.response.data.deletevlaniprangeresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
})
|
||||
},
|
||||
handleAddIpRange (e) {
|
||||
this.form.validateFields((error, values) => {
|
||||
if (error) return
|
||||
|
||||
this.componentLoading = true
|
||||
this.addIpRangeModal = false
|
||||
api('createVlanIpRange', {
|
||||
zoneId: this.resource.zoneid,
|
||||
vlan: values.vlan,
|
||||
gateway: values.gateway,
|
||||
netmask: values.netmask,
|
||||
startip: values.startip,
|
||||
endip: values.endip,
|
||||
forsystemvms: values.forsystemvms,
|
||||
account: values.forsystemvms ? null : values.account,
|
||||
domainid: values.forsystemvms ? null : values.domain,
|
||||
forvirtualnetwork: true
|
||||
}).then(() => {
|
||||
this.$notification.success({
|
||||
message: 'Successfully added IP Range'
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.createvlaniprangeresponse
|
||||
? error.response.data.createvlaniprangeresponse.errortext : error.response.data.errorresponse.errortext,
|
||||
duration: 0
|
||||
})
|
||||
}).finally(() => {
|
||||
this.componentLoading = false
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
&__item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__data {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__col {
|
||||
flex-basis: calc((100% / 3) - 20px);
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-list-item {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
button {
|
||||
&:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form {
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
&:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
398
ui/src/views/infra/network/IpRangesTabStorage.vue
Normal file
398
ui/src/views/infra/network/IpRangesTabStorage.vue
Normal file
@ -0,0 +1,398 @@
|
||||
// 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="componentLoading">
|
||||
<a-button
|
||||
type="dashed"
|
||||
icon="plus"
|
||||
style="margin-bottom: 20px; width: 100%"
|
||||
@click="handleOpenAddIpRangeModal">
|
||||
{{ $t('label.add.ip.range') }}
|
||||
</a-button>
|
||||
|
||||
<a-table
|
||||
style="overflow-y: auto"
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:dataSource="items"
|
||||
:rowKey="record => record.id"
|
||||
:pagination="false"
|
||||
>
|
||||
<template slot="name" slot-scope="record">
|
||||
<div>{{ returnPodName(record.podid) }}</div>
|
||||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<a-popover placement="bottom">
|
||||
<template slot="content">{{ $t('label.remove.ip.range') }}</template>
|
||||
<a-button
|
||||
icon="delete"
|
||||
shape="round"
|
||||
type="danger"
|
||||
@click="handleDeleteIpRange(record.id)"></a-button>
|
||||
</a-popover>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
class="row-element pagination"
|
||||
size="small"
|
||||
style="overflow-y: auto"
|
||||
:current="page"
|
||||
:pageSize="pageSize"
|
||||
:total="items.length"
|
||||
:showTotal="total => `Total ${total} items`"
|
||||
:pageSizeOptions="['10', '20', '40', '80', '100']"
|
||||
@change="changePage"
|
||||
@showSizeChange="changePageSize"
|
||||
showSizeChanger/>
|
||||
|
||||
<a-modal v-model="addIpRangeModal" :title="$t('label.add.ip.range')" @ok="handleAddIpRange">
|
||||
<a-form
|
||||
:form="form"
|
||||
@submit="handleAddIpRange"
|
||||
layout="vertical"
|
||||
class="form"
|
||||
>
|
||||
<a-form-item :label="$t('podId')" class="form__item">
|
||||
<a-select
|
||||
v-decorator="['pod', {
|
||||
rules: [{ required: true, message: 'Required' }]
|
||||
}]"
|
||||
>
|
||||
<a-select-option v-for="pod in pods" :key="pod.id" :value="pod.id">{{ pod.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('gateway')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['gateway', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('netmask')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['netmask', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('vlan')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['vlan']">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('startip')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['startip', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('endip')" class="form__item">
|
||||
<a-input
|
||||
v-decorator="['endip', { rules: [{ required: true, message: 'Required' }] }]">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'IpRangesTabStorage',
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
componentLoading: false,
|
||||
items: [],
|
||||
pods: [],
|
||||
domains: [],
|
||||
domainsLoading: false,
|
||||
addIpRangeModal: false,
|
||||
defaultSelectedPod: null,
|
||||
columns: [
|
||||
{
|
||||
title: this.$t('podId'),
|
||||
scopedSlots: { customRender: 'name' }
|
||||
},
|
||||
{
|
||||
title: this.$t('gateway'),
|
||||
dataIndex: 'gateway'
|
||||
},
|
||||
{
|
||||
title: this.$t('netmask'),
|
||||
dataIndex: 'netmask'
|
||||
},
|
||||
{
|
||||
title: this.$t('vlan'),
|
||||
dataIndex: 'vlanid'
|
||||
},
|
||||
{
|
||||
title: this.$t('startip'),
|
||||
dataIndex: 'startip'
|
||||
},
|
||||
{
|
||||
title: this.$t('endip'),
|
||||
dataIndex: 'endip'
|
||||
},
|
||||
{
|
||||
title: this.$t('action'),
|
||||
scopedSlots: { customRender: 'actions' }
|
||||
}
|
||||
],
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.form = this.$form.createForm(this)
|
||||
},
|
||||
mounted () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
resource (newItem, oldItem) {
|
||||
if (!newItem || !newItem.id) {
|
||||
return
|
||||
}
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.fetchPods()
|
||||
this.componentLoading = true
|
||||
api('listStorageNetworkIpRange', {
|
||||
zoneid: this.resource.zoneid,
|
||||
page: this.page,
|
||||
pageSize: this.pageSize
|
||||
}).then(response => {
|
||||
this.items = response.liststoragenetworkiprangeresponse.storagenetworkiprange ? response.liststoragenetworkiprangeresponse.storagenetworkiprange : []
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.liststoragenetworkiprangeresponse
|
||||
? error.response.data.liststoragenetworkiprangeresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.componentLoading = false
|
||||
})
|
||||
},
|
||||
fetchPods () {
|
||||
this.componentLoading = true
|
||||
api('listPods', {
|
||||
zoneid: this.resource.zoneid
|
||||
}).then(response => {
|
||||
this.pods = response.listpodsresponse.pod ? response.listpodsresponse.pod : []
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.listpodsresponse
|
||||
? error.response.data.listpodsresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.componentLoading = false
|
||||
})
|
||||
},
|
||||
returnPodName (id) {
|
||||
const match = this.pods.find(i => i.id === id)
|
||||
return match ? match.name : null
|
||||
},
|
||||
handleOpenAddIpRangeModal () {
|
||||
this.addIpRangeModal = true
|
||||
setTimeout(() => {
|
||||
if (this.items.length > 0) {
|
||||
this.form.setFieldsValue({
|
||||
pod: this.pods[0].id
|
||||
})
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
handleDeleteIpRange (id) {
|
||||
this.componentLoading = true
|
||||
api('deleteStorageNetworkIpRange', { id }).then(response => {
|
||||
this.$store.dispatch('AddAsyncJob', {
|
||||
title: `Successfully removed IP Range`,
|
||||
jobid: response.deletestoragenetworkiprangeresponse.jobid,
|
||||
status: 'progress'
|
||||
})
|
||||
this.$pollJob({
|
||||
jobId: response.deletestoragenetworkiprangeresponse.jobid,
|
||||
successMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
},
|
||||
errorMessage: 'Removing failed',
|
||||
errorMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
},
|
||||
loadingMessage: `Removing IP Range...`,
|
||||
catchMessage: 'Error encountered while fetching async job result',
|
||||
catchMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
}
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.deletestoragenetworkiprangeresponse
|
||||
? error.response.data.deletestoragenetworkiprangeresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
})
|
||||
},
|
||||
handleAddIpRange (e) {
|
||||
this.form.validateFields((error, values) => {
|
||||
if (error) return
|
||||
|
||||
this.componentLoading = true
|
||||
this.addIpRangeModal = false
|
||||
api('createStorageNetworkIpRange', {
|
||||
podid: values.pod,
|
||||
zoneid: this.resource.zoneid,
|
||||
gateway: values.gateway,
|
||||
netmask: values.netmask,
|
||||
startip: values.startip,
|
||||
endip: values.endip,
|
||||
vlan: values.vlan || null
|
||||
}).then(response => {
|
||||
this.$store.dispatch('AddAsyncJob', {
|
||||
title: `Successfully added IP Range`,
|
||||
jobid: response.createstoragenetworkiprangeresponse.jobid,
|
||||
status: 'progress'
|
||||
})
|
||||
this.$pollJob({
|
||||
jobId: response.createstoragenetworkiprangeresponse.jobid,
|
||||
successMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
},
|
||||
errorMessage: 'Adding failed',
|
||||
errorMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
},
|
||||
loadingMessage: `Adding IP Range...`,
|
||||
catchMessage: 'Error encountered while fetching async job result',
|
||||
catchMethod: () => {
|
||||
this.componentLoading = false
|
||||
this.fetchData()
|
||||
}
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `Error ${error.response.status}`,
|
||||
description: error.response.data.createstoragenetworkiprangeresponse
|
||||
? error.response.data.createstoragenetworkiprangeresponse.errortext : error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.componentLoading = false
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
&__item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__data {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__col {
|
||||
flex-basis: calc((100% / 3) - 20px);
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
}
|
||||
}
|
||||
|
||||
.ant-list-item {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
button {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form {
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
&:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__item {
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@ -17,18 +17,34 @@
|
||||
|
||||
<template>
|
||||
<a-spin :spinning="fetchLoading">
|
||||
<a-tabs :animated="false" defaultActiveKey="0" tabPosition="left">
|
||||
<a-tabs :tabPosition="device === 'mobile' ? 'top' : 'left'" :animated="false">
|
||||
<a-tab-pane v-for="(item, index) in traffictypes" :tab="item.traffictype" :key="index">
|
||||
<div>
|
||||
<strong>{{ $t('id') }}</strong> {{ item.id }}
|
||||
</div>
|
||||
<div v-for="(type, idx) in ['kvmnetworklabel', 'vmwarenetworklabel', 'xennetworklabel', 'hypervnetworklabel', 'ovm3networklabel']" :key="idx">
|
||||
<strong>{{ $t(type) }}</strong>
|
||||
{{ item[type] || 'Use default gateway' }}
|
||||
<div
|
||||
v-for="(type, idx) in ['kvmnetworklabel', 'vmwarenetworklabel', 'xennetworklabel', 'hypervnetworklabel', 'ovm3networklabel']"
|
||||
:key="idx"
|
||||
style="margin-bottom: 10px;">
|
||||
<div><strong>{{ $t(type) }}</strong></div>
|
||||
<div>{{ item[type] || 'Use default gateway' }}</div>
|
||||
</div>
|
||||
<div v-if="item.traffictype === 'Public'">
|
||||
Insert here form/component to manage public IP ranges
|
||||
<IpRangesTab :resource="resource" />
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div><strong>{{ $t('traffictype') }}</strong></div>
|
||||
<div>{{ publicNetwork.traffictype }}</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<div><strong>{{ $t('broadcastdomaintype') }}</strong></div>
|
||||
<div>{{ publicNetwork.broadcastdomaintype }}</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<IpRangesTabPublic :resource="resource" :loading="loading" :network="publicNetwork" />
|
||||
</div>
|
||||
<div v-if="item.traffictype === 'Management'">
|
||||
<a-divider />
|
||||
<IpRangesTabManagement :resource="resource" :loading="loading" />
|
||||
</div>
|
||||
<div v-if="item.traffictype === 'Storage'">
|
||||
<a-divider />
|
||||
<IpRangesTabStorage :resource="resource" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="Service Providers" key="nsp">
|
||||
@ -45,15 +61,21 @@
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import { mixinDevice } from '@/utils/mixin.js'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import IpRangesTab from './IpRangesTab'
|
||||
import IpRangesTabPublic from './IpRangesTabPublic'
|
||||
import IpRangesTabManagement from './IpRangesTabManagement'
|
||||
import IpRangesTabStorage from './IpRangesTabStorage'
|
||||
|
||||
export default {
|
||||
name: 'NetworkTab',
|
||||
components: {
|
||||
IpRangesTab,
|
||||
IpRangesTabPublic,
|
||||
IpRangesTabManagement,
|
||||
IpRangesTabStorage,
|
||||
Status
|
||||
},
|
||||
mixins: [mixinDevice],
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
@ -68,6 +90,7 @@ export default {
|
||||
return {
|
||||
traffictypes: [],
|
||||
nsps: [],
|
||||
publicNetwork: {},
|
||||
fetchLoading: false
|
||||
}
|
||||
},
|
||||
@ -95,6 +118,23 @@ export default {
|
||||
this.fetchLoading = false
|
||||
})
|
||||
|
||||
this.fetchLoading = true
|
||||
api('listNetworks', {
|
||||
listAll: true,
|
||||
trafficType: 'Public',
|
||||
isSystem: true,
|
||||
zoneId: this.resource.zoneid
|
||||
}).then(json => {
|
||||
this.publicNetwork = json.listnetworksresponse.network[0] || {}
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: 'Request Failed',
|
||||
description: error.response.headers['x-description']
|
||||
})
|
||||
}).finally(() => {
|
||||
this.fetchLoading = false
|
||||
})
|
||||
|
||||
this.fetchLoading = true
|
||||
api('listNetworkServiceProviders', { physicalnetworkid: this.resource.id }).then(json => {
|
||||
this.nsps = json.listnetworkserviceprovidersresponse.networkserviceprovider
|
||||
|
||||
154
ui/src/views/infra/zone/PhysicalNetworksTab.vue
Normal file
154
ui/src/views/infra/zone/PhysicalNetworksTab.vue
Normal file
@ -0,0 +1,154 @@
|
||||
// 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-list class="list">
|
||||
<a-list-item v-for="network in networks" :key="network.id" class="list__item">
|
||||
<div class="list__item-outer-container">
|
||||
<div class="list__item-container">
|
||||
<div class="list__col">
|
||||
<div class="list__label">
|
||||
{{ $t('name') }}
|
||||
</div>
|
||||
<div>
|
||||
<router-link :to="{ path: '/physicalnetwork/' + network.id }">{{ network.name }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">{{ $t('state') }}</div>
|
||||
<div><status :text="network.state" displayText></status></div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">
|
||||
{{ $t('isolationmethods') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ network.isolationmethods }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">
|
||||
{{ $t('vlan') }}
|
||||
</div>
|
||||
<div>{{ network.vlan }}</div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">
|
||||
{{ $t('broadcastdomainrange') }}
|
||||
</div>
|
||||
<div>{{ network.broadcastdomainrange }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import Status from '@/components/widgets/Status'
|
||||
|
||||
export default {
|
||||
name: 'PhysicalNetworksTab',
|
||||
components: {
|
||||
Status
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
networks: [],
|
||||
fetchLoading: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
resource (newItem, oldItem) {
|
||||
if (!newItem || !newItem.id) {
|
||||
return
|
||||
}
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.fetchLoading = true
|
||||
api('listPhysicalNetworks', { zoneid: this.resource.id }).then(json => {
|
||||
this.networks = json.listphysicalnetworksresponse.physicalnetwork || []
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: 'Request Failed',
|
||||
description: error.response.headers['x-description']
|
||||
})
|
||||
}).finally(() => {
|
||||
this.fetchLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
|
||||
&__label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__col {
|
||||
flex: 1;
|
||||
|
||||
@media (min-width: 480px) {
|
||||
&:not(:last-child) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
margin-right: -8px;
|
||||
align-items: flex-start;
|
||||
|
||||
&-outer-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 480px) {
|
||||
flex-direction: row;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
162
ui/src/views/infra/zone/SystemVmsTab.vue
Normal file
162
ui/src/views/infra/zone/SystemVmsTab.vue
Normal file
@ -0,0 +1,162 @@
|
||||
// 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-list class="list">
|
||||
<a-list-item v-for="vm in vms" :key="vm.id" class="list__item">
|
||||
<div class="list__item-outer-container">
|
||||
<div class="list__item-container">
|
||||
<div class="list__col">
|
||||
<div class="list__label">
|
||||
{{ $t('name') }}
|
||||
</div>
|
||||
<div>
|
||||
<router-link :to="{ path: '/systemvm/' + vm.id }">{{ vm.name }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">{{ $t('vmstate') }}</div>
|
||||
<div><status :text="vm.state" displayText></status></div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">{{ $t('agentstate') }}</div>
|
||||
<div><status :text="vm.agentstate || 'Unknown'" displayText></status></div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">
|
||||
{{ $t('type') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ vm.systemvmtype == 'consoleproxy' ? 'Console Proxy VM' : 'Secondary Storage VM' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">
|
||||
{{ $t('publicip') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ vm.publicip }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list__col">
|
||||
<div class="list__label">
|
||||
{{ $t('hostname') }}
|
||||
</div>
|
||||
<div>
|
||||
<router-link :to="{ path: '/host/' + vm.hostid }">{{ vm.hostname }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import Status from '@/components/widgets/Status'
|
||||
|
||||
export default {
|
||||
name: 'SystemVmsTab',
|
||||
components: {
|
||||
Status
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
vms: [],
|
||||
fetchLoading: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
resource (newItem, oldItem) {
|
||||
if (!newItem || !newItem.id) {
|
||||
return
|
||||
}
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.fetchLoading = true
|
||||
api('listSystemVms', { zoneid: this.resource.id }).then(json => {
|
||||
this.vms = json.listsystemvmsresponse.systemvm || []
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: 'Request Failed',
|
||||
description: error.response.headers['x-description']
|
||||
})
|
||||
}).finally(() => {
|
||||
this.fetchLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
|
||||
&__label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__col {
|
||||
flex: 1;
|
||||
|
||||
@media (min-width: 480px) {
|
||||
&:not(:last-child) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
margin-right: -8px;
|
||||
align-items: flex-start;
|
||||
|
||||
&-outer-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 480px) {
|
||||
flex-direction: row;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user