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'],
|
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'],
|
details: ['name', 'id', 'allocationstate', 'networktype', 'guestcidraddress', 'localstorageenabled', 'securitygroupsenabled', 'dns1', 'dns2', 'internaldns1', 'internaldns2'],
|
||||||
related: [{
|
related: [{
|
||||||
name: 'physicalnetwork',
|
|
||||||
title: 'Physical Networks',
|
|
||||||
param: 'zoneid'
|
|
||||||
}, {
|
|
||||||
name: 'pod',
|
name: 'pod',
|
||||||
title: 'Pods',
|
title: 'Pods',
|
||||||
param: 'zoneid'
|
param: 'zoneid'
|
||||||
@ -38,10 +34,6 @@ export default {
|
|||||||
name: 'host',
|
name: 'host',
|
||||||
title: 'Hosts',
|
title: 'Hosts',
|
||||||
param: 'zoneid'
|
param: 'zoneid'
|
||||||
}, {
|
|
||||||
name: 'systemvm',
|
|
||||||
title: 'SystemVMs',
|
|
||||||
param: 'zoneid'
|
|
||||||
}, {
|
}, {
|
||||||
name: 'storagepool',
|
name: 'storagepool',
|
||||||
title: 'Primate Storage',
|
title: 'Primate Storage',
|
||||||
@ -54,9 +46,15 @@ export default {
|
|||||||
tabs: [{
|
tabs: [{
|
||||||
name: 'details',
|
name: 'details',
|
||||||
component: () => import('@/components/view/DetailsTab.vue')
|
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',
|
name: 'resources',
|
||||||
component: () => import('@/views/infra/ZoneResources.vue')
|
component: () => import('@/views/infra/zone/ZoneResources.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
component: () => import('@/components/view/SettingsTab.vue')
|
component: () => import('@/components/view/SettingsTab.vue')
|
||||||
@ -68,7 +66,7 @@ export default {
|
|||||||
label: 'Add Zone',
|
label: 'Add Zone',
|
||||||
listView: true,
|
listView: true,
|
||||||
popup: true,
|
popup: true,
|
||||||
component: () => import('@/views/infra/ZoneWizard.vue')
|
component: () => import('@/views/infra/zone/ZoneWizard.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
api: 'updateZone',
|
api: 'updateZone',
|
||||||
|
|||||||
@ -611,6 +611,7 @@
|
|||||||
"label.recover.vm": "Recover VM",
|
"label.recover.vm": "Recover VM",
|
||||||
"label.refresh.blades": "Refresh Blades",
|
"label.refresh.blades": "Refresh Blades",
|
||||||
"label.reinstall.vm": "Reinstall VM",
|
"label.reinstall.vm": "Reinstall VM",
|
||||||
|
"label.release.account": "Release from Account",
|
||||||
"label.release.dedicated.cluster": "Release Dedicated Cluster",
|
"label.release.dedicated.cluster": "Release Dedicated Cluster",
|
||||||
"label.release.dedicated.host": "Release Dedicated Host",
|
"label.release.dedicated.host": "Release Dedicated Host",
|
||||||
"label.release.dedicated.pod": "Release Dedicated Pod",
|
"label.release.dedicated.pod": "Release Dedicated Pod",
|
||||||
@ -639,6 +640,7 @@
|
|||||||
"label.secondary.storage.vm":"Secondary storage VM",
|
"label.secondary.storage.vm":"Secondary storage VM",
|
||||||
"label.service.offering":"Service Offering",
|
"label.service.offering":"Service Offering",
|
||||||
"label.set.default.NIC": "Set default NIC",
|
"label.set.default.NIC": "Set default NIC",
|
||||||
|
"label.set.reservation": "Set Reservation",
|
||||||
"label.shutdown.provider": "Shutdown provider",
|
"label.shutdown.provider": "Shutdown provider",
|
||||||
"label.snapshot.schedule": "Set up Recurring Snapshot",
|
"label.snapshot.schedule": "Set up Recurring Snapshot",
|
||||||
"label.standard.us.keyboard": "Standard (US) keyboard",
|
"label.standard.us.keyboard": "Standard (US) keyboard",
|
||||||
@ -1011,7 +1013,7 @@
|
|||||||
"vmdisplayname": "VM display name",
|
"vmdisplayname": "VM display name",
|
||||||
"vmipaddress": "VM IP Address",
|
"vmipaddress": "VM IP Address",
|
||||||
"vmname": "VM Name",
|
"vmname": "VM Name",
|
||||||
"vmstate": "VM state",
|
"vmstate": "VM State",
|
||||||
"vmtotal": "Total of VMs",
|
"vmtotal": "Total of VMs",
|
||||||
"vmwaredcId": "VMware Datacenter ID",
|
"vmwaredcId": "VMware Datacenter ID",
|
||||||
"vmwaredcName": "VMware Datacenter Name",
|
"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>
|
<template>
|
||||||
<a-spin :spinning="fetchLoading">
|
<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">
|
<a-tab-pane v-for="(item, index) in traffictypes" :tab="item.traffictype" :key="index">
|
||||||
<div>
|
<div
|
||||||
<strong>{{ $t('id') }}</strong> {{ item.id }}
|
v-for="(type, idx) in ['kvmnetworklabel', 'vmwarenetworklabel', 'xennetworklabel', 'hypervnetworklabel', 'ovm3networklabel']"
|
||||||
</div>
|
:key="idx"
|
||||||
<div v-for="(type, idx) in ['kvmnetworklabel', 'vmwarenetworklabel', 'xennetworklabel', 'hypervnetworklabel', 'ovm3networklabel']" :key="idx">
|
style="margin-bottom: 10px;">
|
||||||
<strong>{{ $t(type) }}</strong>
|
<div><strong>{{ $t(type) }}</strong></div>
|
||||||
{{ item[type] || 'Use default gateway' }}
|
<div>{{ item[type] || 'Use default gateway' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.traffictype === 'Public'">
|
<div v-if="item.traffictype === 'Public'">
|
||||||
Insert here form/component to manage public IP ranges
|
<div style="margin-bottom: 10px;">
|
||||||
<IpRangesTab :resource="resource" />
|
<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>
|
</div>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane tab="Service Providers" key="nsp">
|
<a-tab-pane tab="Service Providers" key="nsp">
|
||||||
@ -45,15 +61,21 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { api } from '@/api'
|
import { api } from '@/api'
|
||||||
|
import { mixinDevice } from '@/utils/mixin.js'
|
||||||
import Status from '@/components/widgets/Status'
|
import Status from '@/components/widgets/Status'
|
||||||
import IpRangesTab from './IpRangesTab'
|
import IpRangesTabPublic from './IpRangesTabPublic'
|
||||||
|
import IpRangesTabManagement from './IpRangesTabManagement'
|
||||||
|
import IpRangesTabStorage from './IpRangesTabStorage'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NetworkTab',
|
name: 'NetworkTab',
|
||||||
components: {
|
components: {
|
||||||
IpRangesTab,
|
IpRangesTabPublic,
|
||||||
|
IpRangesTabManagement,
|
||||||
|
IpRangesTabStorage,
|
||||||
Status
|
Status
|
||||||
},
|
},
|
||||||
|
mixins: [mixinDevice],
|
||||||
props: {
|
props: {
|
||||||
resource: {
|
resource: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -68,6 +90,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
traffictypes: [],
|
traffictypes: [],
|
||||||
nsps: [],
|
nsps: [],
|
||||||
|
publicNetwork: {},
|
||||||
fetchLoading: false
|
fetchLoading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -95,6 +118,23 @@ export default {
|
|||||||
this.fetchLoading = false
|
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
|
this.fetchLoading = true
|
||||||
api('listNetworkServiceProviders', { physicalnetworkid: this.resource.id }).then(json => {
|
api('listNetworkServiceProviders', { physicalnetworkid: this.resource.id }).then(json => {
|
||||||
this.nsps = json.listnetworkserviceprovidersresponse.networkserviceprovider
|
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