infra: add zone wizard (#167)

Add zone wizard with basic zone support hidden.

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
Co-authored-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Hoang Nguyen 2020-03-25 16:10:17 +07:00 committed by Rohit Yadav
parent c6e70d3169
commit bc13fe2499
13 changed files with 5862 additions and 11 deletions

View File

@ -563,6 +563,7 @@
"label.enable.vnmc.provider": "Enable VNMC provider",
"label.enable.vpc.offering": "Enable VPC offering",
"label.enable.vpn": "Enable Remote Access VPN",
"label.enable.zone": "Enable Zone",
"label.enter.token": "Enter token",
"label.error.volume.upload": "Please choose a file",
"label.error.zone.combined": "All Zones cannot be combined with any other zone",
@ -1085,5 +1086,164 @@
"addNewNetworks": "Add new networks",
"existingNetworks": "Existing networks",
"sshKeyPairs": "SSH keypairs",
"wednesday": "Wednesday"
"wednesday": "Wednesday",
"noselect": "No thanks",
"groupname": "Add to group",
"keyboard": "Keyboard language",
"userdata": "Userdata",
"label.back": "Back",
"label.basic": "Basic",
"label.advanced": "Advanced",
"message.desc.zone": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.",
"message.desc.basic.zone": "Provide a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering).",
"message.desc.advanced.zone": "For more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support.",
"message.advanced.security.group": "Choose this if you wish to use security groups to provide guest VM isolation.",
"message.setup.physical.network.during.zone.creation.basic": "When adding a basic zone, you can set up one physical network, which corresponds to a NIC on the hypervisor. The network carries several types of traffic.<br/><br/>You may also <strong>add</strong> other traffic types onto the physical network.",
"message.setup.physical.network.during.zone.creation": "When adding an advanced zone, you need to set up one or more physical networks. Each network corresponds to a NIC on the hypervisor. Each physical network can carry one or more types of traffic, with certain restrictions on how they may be combined. Add or remove one or more traffic types onto each physical network.",
"message.public.traffic.in.advanced.zone": "Public traffic is generated when VMs in the cloud access the internet. Publicly-accessible IPs must be allocated for this purpose. End users can use the CloudStack UI to acquire these IPs to implement NAT between their guest network and their public network.<br/><br/>Provide at least one range of IP addresses for internet traffic.",
"message.public.traffic.in.basic.zone": "Public traffic is generated when VMs in the cloud access the Internet or provide services to clients over the Internet. Publicly accessible IPs must be allocated for this purpose. When a instance is created, an IP from this set of Public IPs will be allocated to the instance in addition to the guest IP address. Static 1-1 NAT will be set up automatically between the public IP and the guest IP. End users can also use the CloudStack UI to acquire additional IPs to implement static NAT between their instances and the public IP.",
"message.guest.traffic.in.advanced.zone": "Guest network traffic is communication between end-user virtual machines. Specify a range of VLAN IDs to carry guest traffic for each physical network.",
"message.guest.traffic.in.basic.zone": "Guest network traffic is communication between end-user virtual machines. Specify a range of IP addresses that CloudStack can assign to guest VMs. Make sure this range does not overlap the reserved system IP range.",
"message.add.pod.during.zone.creation": "Each zone must contain in one or more pods, and we will add the first pod now. A pod contains hosts and primary storage servers, which you will add in a later step. First, configure a range of reserved IP addresses for CloudStack's internal management traffic. The reserved IP range must be unique for each zone in the cloud.",
"message.desc.cluster": "Each pod must contain one or more clusters, and we will add the first cluster now. A cluster provides a way to group hosts. The hosts in a cluster all have identical hardware, run the same hypervisor, are on the same subnet, and access the same shared storage. Each cluster consists of one or more hosts and one or more primary storage servers.",
"message.desc.host": "Each cluster must contain at least one host (computer) for guest VMs to run on, and we will add the first host now. For a host to function in CloudStack, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the CloudStack management server.<br/><br/>Give the host's DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts.",
"message.creating.zone": "Creating zone",
"message.dedicate.zone": "Dedicating zone",
"message.creating.physical.networks": "Creating physical networks",
"message.configuring.physical.networks": "Configuring physical networks",
"message.adding.Netscaler.provider": "Adding Netscaler provider",
"message.adding.Netscaler.device": "Adding Netscaler device",
"message.creating.pod": "Creating pod",
"message.creating.guest.network": "Creating guest network",
"message.configuring.public.traffic": "Configuring public traffic",
"message.configuring.storage.traffic": "Configuring storage traffic",
"message.configuring.guest.traffic": "Configuring guest traffic",
"message.creating.cluster": "Creating cluster",
"message.adding.host": "Adding host",
"message.creating.primary.storage": "Creating primary storage",
"message.creating.secondary.storage": "Creating secondary storage",
"message.enabling.security.group.provider": "Enabling Security Group provider",
"message.Zone.creation.complete": "Zone creation complete",
"message.launch.zone": "Zone is ready to launch; please proceed to the next step.",
"message.please.wait.while.zone.is.being.created": "Please wait while your zone is being created; this may take a while...",
"message.required.add.least.IP": "Please add at least 1 IP Range",
"message.required.traffic.type": "Error in configuration! All required traffic types should be added and with multiple physical networks each network should have a label.",
"message.desc.primary.storage": "Each cluster must contain one or more primary storage servers, and we will add the first one now. Primary storage contains the disk volumes for all the VMs running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.",
"message.desc.secondary.storage": "Each zone must have at least one NFS or secondary storage server, and we will add the first one now. Secondary storage stores VM templates, ISO images, and VM disk volume snapshots. This server must be available to all hosts in the zone.<br/><br/>Provide the IP address and exported path.",
"label.name": "Name",
"label.ipv4.dns1": "IPv4 DNS1",
"label.ipv4.dns2": "IPv4 DNS2",
"label.ipv6.dns1": "IPv6 DNS1",
"label.ipv6.dns2": "IPv6 DNS2",
"label.internal.dns.1": "Internal DNS 1",
"label.internal.dns.2": "Internal DNS 2",
"label.hypervisor": "Hypervisor",
"label.network.offering": "Network Offering",
"label.network.domain": "Network Domain",
"label.guest.cidr": "Guest CIDR",
"label.dedicated": "Dedicated",
"label.domains": "Domains",
"label.account": "Account",
"label.local.storage.enabled": "Enable local storage for User VMs",
"label.local.storage.enabled.system.vms": "Enable local storage for System VMs",
"label.menu.security.groups": "Security Groups",
"label.network.name": "Network Name",
"label.isolation.method": "Isolation method",
"label.traffic.types": "Traffic Types",
"label.previous": "Previous",
"label.next": "Next",
"label.zone.type": "Zone Type",
"label.zone.details": "Zone details",
"label.network": "Network",
"label.add.resources": "Add Resources",
"label.launch": "Launch",
"label.physical.network": "Physical Network",
"label.netScaler": "NetScaler",
"label.public.traffic": "Public traffic",
"label.pod": "Pod",
"label.guest.traffic": "Guest Traffic",
"label.storage.traffic": "Storage Traffic",
"label.guest.ip": "Guest IP Address",
"label.username": "Username",
"label.password": "Password",
"label.type": "Type",
"label.public.interface": "Public Interface",
"label.private.interface": "Private Interface",
"label.gslb.service": "GSLB service",
"label.gslb.service.public.ip": "GSLB service Public IP",
"label.gslb.service.private.ip": "GSLB service Private IP",
"label.numretries": "Number of Retries",
"label.capacity": "Capacity",
"label.please.specify.netscaler.info": "Please specify Netscaler info",
"label.zoneWizard.trafficType.management": "Management: Traffic between CloudStack's internal resources, including any components that communicate with the Management Server, such as hosts and CloudStack system VMs",
"label.pod.name": "Pod name",
"label.reserved.system.gateway": "Reserved system gateway",
"label.reserved.system.netmask": "Reserved system netmask",
"label.start.reserved.system.IP": "Start Reserved system IP",
"label.end.reserved.system.IP": "End Reserved system IP",
"label.guest.gateway": "Guest Gateway",
"label.guest.netmask": "Guest Netmask",
"label.guest.start.ip": "Guest start IP",
"label.guest.end.ip": "Guest end IP",
"label.cluster": "Cluster",
"label.host": "Host",
"label.cluster.name": "Cluster Name",
"label.vcenter.host": "vCenter Host",
"label.vcenter.password": "vCenter Password",
"label.vcenter.username": "vCenter Username",
"label.vcenter.datacenter": "vCenter Datacenter",
"label.vcenter.datastore": "vCenter Datastore",
"label.override.public.traffic": "Override Public-Traffic",
"label.override.guest.traffic": "Override Guest-Traffic",
"label.cisco.nexus1000v.ip.address": "Nexus 1000v IP Address",
"label.cisco.nexus1000v.password": "Nexus 1000v Password",
"label.cisco.nexus1000v.username": "Nexus 1000v Username",
"label.host.name": "Host Name",
"label.agent.username": "Agent Username",
"label.agent.password": "Agent Password",
"label.tags": "Tags",
"label.scope": "Scope",
"label.protocol": "Protocol",
"label.server": "Server",
"label.path": "Path",
"label.SR.name": "SR Name-Label",
"label.target.iqn": "Target IQN",
"label.LUN.number": "LUN #",
"label.smb.domain": "SMB Domain",
"label.smb.username": "SMB Username",
"label.smb.password": "SMB Password",
"label.rados.monitor": "RADOS Monitor",
"label.rados.pool": "RADOS Pool",
"label.rados.user": "RADOS User",
"label.rados.secret": "RADOS Secret",
"label.volgroup": "Volume Group",
"label.volume": "Volume",
"label.storage.tags": "Storage Tags",
"label.provider": "Provider",
"label.s3.access_key": "Access Key",
"label.s3.bucket": "Bucket",
"label.s3.connection_timeout": "Connection Timeout",
"label.s3.endpoint": "Endpoint",
"label.s3.max_error_retry": "Max Error Retry",
"label.s3.nfs.path": "S3 NFS Path",
"label.s3.nfs.server": "S3 NFS Server",
"label.s3.secret_key": "Secret Key",
"label.s3.socket_timeout": "Socket Timeout",
"label.s3.use_https": "Use HTTPS",
"label.create.nfs.secondary.staging.storage": "Create NFS Secondary Staging Store",
"label.create.nfs.secondary.staging.store": "Create NFS secondary staging store",
"label.url": "URL",
"label.key": "Key",
"label.add.physical.network": "Add Physical Network",
"label.delete.confirm": "Delete?",
"label.gateway": "Gateway",
"label.netmask": "Netmask",
"label.start.IP": "Start IP",
"label.end.IP": "End IP",
"label.add.traffic": "Add Traffic",
"label.vlan.range": "VLAN/VNI Range",
"label.launch.zone": "Launch Zone",
"label.done": "Done",
"label.fix.errors": "Fix errors",
"error.something.went.wrong.please.correct.the.following": "Something went wrong; please correct the following"
}

View File

@ -25,7 +25,7 @@ import { ACCESS_TOKEN, CURRENT_PROJECT } from '@/store/mutation-types'
const service = axios.create({
baseURL: config.apiBase,
timeout: 60000
timeout: 600000
})
const err = (error) => {

View File

@ -0,0 +1,216 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div>
<a-card
class="ant-form-text"
style="text-align: justify; margin: 10px 0; padding: 24px;"
v-if="description && description.length > 0"
v-html="$t(description)">
</a-card>
<a-form
class="form-content"
:form="form"
@submit="handleSubmit">
<a-form-item
:label="$t('label.vlan.range')"
v-bind="formItemLayout"
:help="validMessage"
:validate-status="validStatus"
has-feedback>
<a-form-item
has-feedback
:style="{ display: 'inline-block', width: 'calc(50% - 12px)' }">
<a-input-number
v-decorator="['vlanRangeStart', {
rules: [{
validator: validateFromTo,
fromInput: true,
compare: 'vlanRangeEnd',
initialValue: getPrefilled('vlanRangeStart')
}]
}]"
style="width: 100%;"
/>
</a-form-item>
<span :style="{ display: 'inline-block', width: '24px', textAlign: 'center' }">
-
</span>
<a-form-item
has-feedback
:style="{ display: 'inline-block', width: 'calc(50% - 12px)' }">
<a-input-number
v-decorator="['vlanRangeEnd', {
rules: [{
validator: validateFromTo,
toInput: true,
compare: 'vlanRangeStart',
initialValue: getPrefilled('vlanRangeEnd')
}]
}]"
style="width: 100%;"
/>
</a-form-item>
</a-form-item>
</a-form>
<div class="form-action">
<a-button
v-if="!isFixError"
class="button-prev"
@click="handleBack">
{{ $t('label.previous') }}
</a-button>
<a-button class="button-next" type="primary" @click="handleSubmit">
{{ $t('label.next') }}
</a-button>
</div>
</div>
</template>
<script>
export default {
name: 'AdvancedGuestTrafficForm',
props: {
prefillContent: {
type: Object,
default: function () {
return {}
}
},
description: {
type: String,
default: 'Creating IP Ranges'
},
isFixError: {
type: Boolean,
default: false
}
},
data () {
return {
formItemLayout: {
labelCol: { span: 8 },
wrapperCol: { span: 12 }
},
validStatus: '',
validMessage: ''
}
},
created () {
this.form = this.$form.createForm(this, {
onFieldsChange: (_, changedFields) => {
this.$emit('fieldsChanged', changedFields)
}
})
},
mounted () {
this.fillValue()
},
methods: {
fillValue () {
const fieldVal = {}
fieldVal.vlanRangeStart = this.getPrefilled('vlanRangeStart')
this.form.setFieldsValue(fieldVal)
fieldVal.vlanRangeEnd = this.getPrefilled('vlanRangeEnd')
this.form.setFieldsValue(fieldVal)
},
getPrefilled (key) {
return this.prefillContent[key] ? this.prefillContent[key].value : null
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
this.validStatus = ''
this.validMessage = ''
if (err) {
return
}
if (!this.checkFromTo(values.vlanRangeStart, values.vlanRangeEnd)) {
this.validStatus = 'error'
this.validMessage = 'Please enter a valid VLAN/VNI range'
return
}
if (this.isFixError) {
this.$emit('submitLaunchZone')
return
}
this.$emit('nextPressed')
})
},
handleBack (e) {
this.$emit('backPressed')
},
validateFromTo (rule, value, callback) {
let fromVal = ''
let toVal = ''
this.validStatus = ''
this.validMessage = ''
if (rule.fromInput) {
fromVal = value
toVal = this.form.getFieldValue(rule.compare)
} else if (rule.toInput) {
toVal = value
fromVal = this.form.getFieldValue(rule.compare)
}
if (!this.checkFromTo(fromVal, toVal)) {
this.validStatus = 'error'
this.validMessage = 'Please enter a valid VLAN/VNI range'
}
callback()
},
checkFromTo (fromVal, toVal) {
if (!fromVal) fromVal = 0
if (!toVal) toVal = 0
if (fromVal > toVal) {
return false
}
return true
}
}
}
</script>
<style scoped lang="less">
.form-content {
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
vertical-align: center;
padding-top: 16px;
padding-top: 16px;
margin-top: 8px;
max-height: 300px;
overflow-y: auto;
/deep/.has-error {
.ant-form-explain {
text-align: left;
}
}
/deep/.ant-form-item-control {
text-align: left;
}
}
.form-action {
margin-top: 16px;
}
</style>

View File

@ -0,0 +1,258 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div>
<a-card
class="ant-form-text"
style="text-align: justify; margin: 10px 0; padding: 24px;"
v-html="$t(description)">
</a-card>
<a-table
bordered
:dataSource="ipRanges"
:columns="columns"
:pagination="false"
style="margin-bottom: 24px;" >
<template slot="actions" slot-scope="text, record">
<a-button type="danger" shape="circle" icon="delete" @click="onDelete(record.key)" />
</template>
<template slot="footer">
<a-form
layout="inline"
:form="form"
@submit="handleAddRange">
<a-form-item :style="{ display: 'inline-block', width: '14%' }">
<a-input
v-decorator="[ 'gateway', {
rules: [{ required: true, message: 'Please enter Gateway' }]
}]"
:placeholder="$t('label.gateway')"
/>
</a-form-item>
<a-form-item :style="{ display: 'inline-block', width: '14%' }">
<a-input
v-decorator="[ 'netmask', {
rules: [{ required: true, message: 'Please enter Netmask' }]
}]"
:placeholder="$t('label.netmask')"
/>
</a-form-item>
<a-form-item :style="{ display: 'inline-block', width: '14%' }">
<a-input
v-decorator="[ 'vlan', { rules: [{ required: false }] }]"
:placeholder="$t('label.vlan')"
/>
</a-form-item>
<a-form-item :style="{ display: 'inline-block', width: '14%' }">
<a-input
v-decorator="[ 'startIp', {
rules: [
{
required: true,
message: 'Please enter Start IP'
},
{
validator: checkIpFormat,
ipV4: true,
message: 'Please enter a valid IPv4 address.'
}
]
}]"
:placeholder="$t('label.start.IP')"
/>
</a-form-item>
<a-form-item :style="{ display: 'inline-block', width: '14%' }">
<a-input
v-decorator="[ 'endIp', {
rules: [
{
required: true,
message: 'Please enter End IP'
},
{
validator: checkIpFormat,
ipV4: true,
message: 'Please enter a valid IPv4 address.'
}]
}]"
:placeholder="$t('label.end.IP')"
/>
</a-form-item>
<a-form-item :style="{ display: 'inline-block', width: '14%' }">
<a-button type="primary" html-type="submit">{{ $t('label.add') }}</a-button>
</a-form-item>
</a-form>
</template>
</a-table>
<div class="form-action">
<a-button
v-if="!isFixError"
class="button-prev"
@click="handleBack">
{{ $t('label.previous') }}
</a-button>
<a-button class="button-next" type="primary" @click="handleSubmit">
{{ $t('label.next') }}
</a-button>
</div>
<a-modal
:visible="showError"
title="Error!"
@ok="() => { showError = false }"
@cancel="() => { showError = false }"
centered
>
<span>{{ $t('message.required.add.least.IP') }}</span>
</a-modal>
</div>
</template>
<script>
export default {
props: {
traffic: {
type: String,
default: '0'
},
description: {
type: String,
default: 'Creating IP Ranges'
},
prefillContent: {
type: Object,
default: function () {
return {}
}
},
isFixError: {
type: Boolean,
default: false
}
},
data () {
return {
formItemLayout: {
wrapperCol: { span: 0 }
},
ipRanges: [],
columns: [
{
title: this.$t('label.gateway'),
dataIndex: 'gateway',
width: 150
},
{
title: this.$t('label.netmask'),
dataIndex: 'netmask',
width: 150
},
{
title: this.$t('label.vlan'),
dataIndex: 'vlan',
width: 120
},
{
title: this.$t('label.start.IP'),
dataIndex: 'startIp',
width: 130
},
{
title: this.$t('label.end.IP'),
dataIndex: 'endIp',
width: 130
},
{
title: '',
dataIndex: 'actions',
scopedSlots: { customRender: 'actions' },
width: 50
}
],
showError: false,
ipV4Regex: /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i,
ipV6Regex: /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i
}
},
mounted () {
const prefilledIpRangesKey = this.traffic + '-ipranges'
if (this.prefillContent[prefilledIpRangesKey]) {
this.ipRanges = this.prefillContent[prefilledIpRangesKey]
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
},
methods: {
handleAddRange (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
this.ipRanges.push({
key: this.ipRanges.length.toString(),
gateway: values.gateway,
netmask: values.netmask,
vlan: values.vlan,
startIp: values.startIp,
endIp: values.endIp
})
this.form.resetFields()
}
})
this.emitIpRanges()
},
isValidSetup () {
return this.ipRanges && this.ipRanges.length > 0
},
handleSubmit (e) {
if (this.isValidSetup()) {
if (this.isFixError) {
this.$emit('submitLaunchZone')
return
}
this.$emit('nextPressed', this.ipRanges)
} else {
this.showError = true
}
},
handleBack (e) {
this.$emit('backPressed')
},
onDelete (key) {
const ipRanges = [...this.ipRanges]
this.ipRanges = ipRanges.filter(item => item.key !== key)
this.emitIpRanges()
},
emitIpRanges () {
const trafficRanges = {}
trafficRanges[this.traffic + '-ipranges'] = this.ipRanges
this.$emit('fieldsChanged', trafficRanges)
},
checkIpFormat (rule, value, callback) {
if (!value || value === '') {
callback()
} else if (rule.ipV4 && !this.ipV4Regex.test(value)) {
callback(rule.message)
} else if (rule.ipV6 && !this.ipV6Regex.test(value)) {
callback(rule.message)
} else {
callback()
}
}
}
}
</script>

View File

@ -0,0 +1,277 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div>
<a-card
class="ant-form-text"
v-if="description && description.length > 0"
v-html="$t(description)">
</a-card>
<a-form
class="form-content"
:form="form"
@submit="handleSubmit">
<a-form-item
v-for="(field, index) in this.fields"
:key="index"
:label="$t(field.title)"
v-if="isDisplayInput(field.display)"
v-bind="formItemLayout"
:has-feedback="field.switch ? false : true">
<a-select
v-if="field.select"
v-decorator="[field.key, {
rules: [
{
required: field.required,
message: field.placeHolder,
initialValue: getPrefilled(field.key)
}
]
}]"
:allowClear="true"
>
<a-select-option
v-for="option in field.options"
:key="option.id"
:value="option.id"
>
{{ option.name || option.description }}
</a-select-option>
</a-select>
<a-switch
v-else-if="field.switch"
v-decorator="[field.key]"
:default-checked="isChecked(field)"
/>
<a-input
v-else-if="field.password"
type="password"
v-decorator="[field.key, {
rules: [
{
required: field.required,
message: field.placeHolder,
initialValue: getPrefilled(field.key)
}
]
}]"
/>
<a-input
v-else
v-decorator="[field.key, {
rules: [
{
required: field.required,
message: field.placeHolder,
initialValue: getPrefilled(field.key)
},
{
validator: checkIpFormat,
ipV4: field.ipV4,
ipV6: field.ipV6,
message: field.message
}
]
}]"
/>
</a-form-item>
</a-form>
<div class="form-action">
<a-button
v-if="!isFixError"
class="button-prev"
@click="handleBack">
{{ $t('label.previous') }}
</a-button>
<a-button class="button-next" type="primary" @click="handleSubmit">
{{ $t('label.next') }}
</a-button>
</div>
</div>
</template>
<script>
export default {
props: {
prefillContent: {
type: Object,
default: function () {
return {}
}
},
fields: {
type: Array,
default: function () {
return []
}
},
description: {
type: String,
default: 'Creating IP Ranges'
},
isFixError: {
type: Boolean,
default: false
}
},
created () {
this.form = this.$form.createForm(this, {
onFieldsChange: (_, changedFields) => {
this.$emit('fieldsChanged', changedFields)
}
})
},
data: () => ({
formItemLayout: {
labelCol: { span: 8 },
wrapperCol: { span: 12 }
},
ipV4Regex: /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i,
ipV6Regex: /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i
}),
mounted () {
this.fillValue(true)
},
watch: {
fields () {
this.fillValue(false)
}
},
methods: {
fillValue (autoFill) {
this.fields.forEach(field => {
const fieldExists = this.isDisplayInput(field.display)
if (!fieldExists) {
return
}
const fieldVal = {}
if (field.key === 'agentUserName' && !this.getPrefilled(field.key)) {
fieldVal[field.key] = 'Oracle'
} else {
fieldVal[field.key] = this.getPrefilled(field.key)
}
if (autoFill) {
this.form.setFieldsValue(fieldVal)
} else {
this.form.getFieldDecorator(field.key, { initialValue: this.getPrefilled(field.key) })
}
})
},
getPrefilled (key) {
return this.prefillContent[key] ? this.prefillContent[key].value : null
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (err) {
return
}
if (this.isFixError) {
this.$emit('submitLaunchZone')
return
}
this.$emit('nextPressed')
})
},
handleBack (e) {
this.$emit('backPressed')
},
checkIpFormat (rule, value, callback) {
if (!value || value === '') {
callback()
} else if (rule.ipV4 && !this.ipV4Regex.test(value)) {
callback(rule.message)
} else if (rule.ipV6 && !this.ipV6Regex.test(value)) {
callback(rule.message)
} else {
callback()
}
},
isDisplayInput (conditions) {
if (!conditions || Object.keys(conditions).length === 0) {
return true
}
let isShow = false
Object.keys(conditions).forEach(key => {
const condition = conditions[key]
const fieldVal = this.form.getFieldValue(key)
? this.form.getFieldValue(key)
: (this.prefillContent[key] ? this.prefillContent[key].value : null)
if (Array.isArray(condition) && condition.includes(fieldVal)) {
isShow = true
return false
} else if (!Array.isArray(condition) && fieldVal === condition) {
isShow = true
return false
}
return true
})
return isShow
},
isChecked (field) {
if (this.prefillContent[field.key] && this.prefillContent[field.key].value) {
return this.prefillContent[field.key].value
}
if (!field.checked) {
return false
}
return true
}
}
}
</script>
<style scoped lang="less">
.form-content {
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
vertical-align: center;
padding-top: 16px;
padding-top: 16px;
margin-top: 8px;
max-height: 300px;
overflow-y: auto;
/deep/.has-error {
.ant-form-explain {
text-align: left;
}
}
/deep/.ant-form-item-control {
text-align: left;
}
}
.ant-form-text {
text-align: justify;
margin: 10px 0;
padding: 24px;
width: 100%;
}
.form-action {
margin-top: 16px;
}
</style>

View File

@ -16,29 +16,205 @@
// under the License.
<template>
<div>
<a-steps direction="vertical" type="navigation" :current="1">
<a-step title="Finished" description="This is a description." />
<a-step title="In Progress" description="This is a description." />
<a-step title="Waiting" description="This is a description." />
<div class="form">
<a-steps
labelPlacement="vertical"
size="small"
:current="currentStep">
<a-step
v-for="(item) in steps"
:key="item.title"
:title="$t(item.title)">
</a-step>
</a-steps>
<div>
<zone-wizard-zone-type-step
v-if="currentStep === 0"
@nextPressed="nextPressed"
@fieldsChanged="onFieldsChanged"
:prefillContent="zoneConfig"
/>
<zone-wizard-zone-details-step
v-else-if="currentStep === 1"
@nextPressed="nextPressed"
@backPressed="backPressed"
@fieldsChanged="onFieldsChanged"
@submitLaunchZone="onLaunchZone"
:isFixError="stepFixError"
:prefillContent="zoneConfig"
/>
<zone-wizard-network-setup-step
v-else-if="currentStep === 2"
@nextPressed="nextPressed"
@backPressed="backPressed"
@fieldsChanged="onFieldsChanged"
@submitLaunchZone="onLaunchZone"
:stepChild="stepChild"
:isFixError="stepFixError"
:prefillContent="zoneConfig"
/>
<zone-wizard-add-resources
v-else-if="currentStep === 3"
@nextPressed="nextPressed"
@backPressed="backPressed"
@fieldsChanged="onFieldsChanged"
@submitLaunchZone="onLaunchZone"
:stepChild="stepChild"
:isFixError="stepFixError"
:prefillContent="zoneConfig"
/>
<zone-wizard-launch-zone
v-else
@backPressed="backPressed"
@closeAction="onCloseAction"
@refresh-data="onRefreshData"
@stepError="onStepError"
:launchZone="launchZone"
:stepChild="stepChild"
:launchData="launchData"
:isFixError="stepFixError"
:prefillContent="zoneConfig"
/>
</div>
</div>
</template>
<script>
import ZoneWizardZoneTypeStep from '@views/infra/zone/ZoneWizardZoneTypeStep'
import ZoneWizardZoneDetailsStep from '@views/infra/zone/ZoneWizardZoneDetailsStep'
import ZoneWizardNetworkSetupStep from '@views/infra/zone/ZoneWizardNetworkSetupStep'
import ZoneWizardAddResources from '@views/infra/zone/ZoneWizardAddResources'
import ZoneWizardLaunchZone from '@views/infra/zone/ZoneWizardLaunchZone'
export default {
name: 'ZoneWizard',
components: {
ZoneWizardZoneTypeStep,
ZoneWizardZoneDetailsStep,
ZoneWizardNetworkSetupStep,
ZoneWizardAddResources,
ZoneWizardLaunchZone
},
data () {
return {
currentStep: 0,
stepFixError: false,
launchZone: false,
launchData: {},
stepChild: '',
steps: [
{
title: 'label.zone.type',
step: [],
description: 'Select type of zone basic/advanced.',
hint: 'This is the type of zone deployement that you want to use. Basic zone: provides a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering). Advanced zone: For more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support.'
},
{
title: 'label.zone.details',
step: ['stepAddZone', 'dedicateZone'],
description: 'Populate zone details',
hint: 'A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.'
},
{
title: 'label.network',
step: ['physicalNetwork', 'netscaler', 'pod', 'guestTraffic', 'storageTraffic', 'publicTraffic'],
description: 'Setup network and traffic',
hint: 'Configure network components and public/guest/management traffic including IP addresses.'
},
{
title: 'label.add.resources',
step: ['clusterResource', 'hostResource', 'primaryResource', 'secondaryResource'],
description: 'Add infrastructure resources',
hint: 'Add infrastructure resources - pods, clusters, primary/secondary storages.'
},
{
title: 'label.launch',
step: ['launchZone'],
description: 'Zone is ready to launch; please proceed to the next step.',
hint: 'Configure network components and traffic including IP addresses.'
}
],
zoneConfig: {}
}
},
methods: {
nextPressed () {
this.currentStep++
},
backPressed (data) {
this.currentStep--
},
onFieldsChanged (data) {
if (data.zoneType &&
this.zoneConfig.zoneType &&
data.zoneType.value !== this.zoneConfig.zoneType.value) {
this.zoneConfig.physicalNetworks = null
}
this.zoneConfig = { ...this.zoneConfig, ...data }
},
onCloseAction () {
this.$emit('close-action')
},
onRefreshData () {
this.$message.success('Processing complete!')
this.$emit('refresh-data')
this.onCloseAction()
},
onStepError (step, launchData) {
this.currentStep = this.steps.findIndex(item => item.step.includes(step))
this.stepChild = step
this.launchData = launchData
this.launchZone = false
this.stepFixError = true
},
onLaunchZone () {
this.stepFixError = false
this.launchZone = true
this.currentStep = this.steps.findIndex(item => item.step.includes('launchZone'))
}
}
}
</script>
<style scoped>
<style scoped lang="scss">
.form {
width: 95vw;
@media (min-width: 1000px) {
width: 800px;
}
/deep/.form-action {
position: relative;
margin-top: 16px;
height: 35px;
}
/deep/.button-next {
position: absolute;
right: 0;
}
/deep/.button-next.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only) {
position: absolute;
right: 0;
}
}
/deep/.ant-form-text {
width: 100%;
}
.steps-content {
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
vertical-align: center;
padding: 8px;
padding-top: 16px;
}
.steps-action {
margin-top: 24px;
}
</style>

View File

@ -0,0 +1,898 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div style="width: auto;">
<a-steps progressDot :current="currentStep" size="small" style="margin-left: 0; margin-top: 16px;">
<a-step
v-for="step in steps"
:key="step.title"
:title="$t(step.title)"></a-step>
</a-steps>
<static-inputs-form
v-if="currentStep === 0"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="clusterFields"
:prefillContent="prefillContent"
:description="steps[currentStep].description"
:isFixError="isFixError"
/>
<div v-if="hypervisor !== 'VMware'">
<static-inputs-form
v-if="currentStep === 1"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="hostFields"
:prefillContent="prefillContent"
:description="steps[currentStep].description"
:isFixError="isFixError"
/>
<static-inputs-form
v-if="currentStep === 2"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="primaryStorageFields"
:prefillContent="prefillContent"
:description="steps[currentStep].description"
:isFixError="isFixError"
/>
<static-inputs-form
v-if="currentStep === 3"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="secondaryStorageFields"
:prefillContent="prefillContent"
:description="steps[currentStep].description"
:isFixError="isFixError"
/>
</div>
<div v-else>
<static-inputs-form
v-if="currentStep === 1"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="primaryStorageFields"
:prefillContent="prefillContent"
:description="steps[currentStep].description"
:isFixError="isFixError"
/>
<static-inputs-form
v-if="currentStep === 2"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="secondaryStorageFields"
:prefillContent="prefillContent"
:description="steps[currentStep].description"
:isFixError="isFixError"
/>
</div>
</div>
</template>
<script>
import StaticInputsForm from '@views/infra/zone/StaticInputsForm'
import { api } from '@/api'
export default {
components: {
StaticInputsForm
},
props: {
prefillContent: {
type: Object,
default: function () {
return {}
}
},
stepChild: {
type: String,
default: ''
},
isFixError: {
type: Boolean,
default: false
}
},
computed: {
zoneType () {
return this.prefillContent.zoneType ? this.prefillContent.zoneType.value : null
},
hypervisor () {
return this.prefillContent.hypervisor ? this.prefillContent.hypervisor.value : null
},
steps () {
const steps = []
const hypervisor = this.prefillContent.hypervisor ? this.prefillContent.hypervisor.value : null
steps.push({
title: 'label.cluster',
fromKey: 'clusterResource',
description: 'message.desc.cluster'
})
if (hypervisor !== 'VMware') {
steps.push({
title: 'label.host',
fromKey: 'hostResource',
description: 'message.desc.host'
})
}
steps.push({
title: 'label.primary.storage',
fromKey: 'primaryResource',
description: 'message.desc.primary.storage'
})
steps.push({
title: 'label.secondary.storage',
fromKey: 'secondaryResource',
description: 'message.desc.secondary.storage'
})
return steps
},
clusterFields () {
return [
{
title: 'label.cluster.name',
key: 'clusterName',
placeHolder: 'Please enter cluster name',
required: true
},
{
title: 'label.vcenter.host',
key: 'vCenterHost',
placeHolder: 'Please enter vCenter Host',
required: true,
display: {
hypervisor: ['VMware', 'Ovm3']
}
},
{
title: 'label.vcenter.username',
key: 'vCenterUsername',
placeHolder: 'Please enter vCenter Username',
required: true,
display: {
hypervisor: ['VMware', 'Ovm3']
}
},
{
title: 'label.vcenter.password',
key: 'vCenterPassword',
placeHolder: 'Please enter vCenter Password',
required: true,
password: true,
display: {
hypervisor: ['VMware', 'Ovm3']
}
},
{
title: 'label.vcenter.datacenter',
key: 'vCenterDatacenter',
placeHolder: 'Please enter vCenter Datacenter',
required: true,
display: {
hypervisor: ['VMware', 'Ovm3']
}
},
{
title: 'label.override.public.traffic',
key: 'overridepublictraffic',
required: false,
switch: true,
display: {
dvSwitchEnabled: true
}
},
{
title: 'label.override.guest.traffic',
key: 'overrideguesttraffic',
required: false,
switch: true,
display: {
dvSwitchEnabled: true
}
},
{
title: 'label.cisco.nexus1000v.ip.address',
key: 'vsmipaddress',
placeHolder: 'Please enter Nexus 1000v IP Address',
required: false,
display: {
vSwitchEnabled: true
}
},
{
title: 'label.cisco.nexus1000v.username',
key: 'vsmusername',
placeHolder: 'Please enter Nexus 1000v Username',
required: false,
display: {
vSwitchEnabled: true
}
},
{
title: 'label.cisco.nexus1000v.password',
key: 'vsmpassword',
placeHolder: 'Please enter Nexus 1000v Password',
required: false,
display: {
vSwitchEnabled: true
}
}
]
},
hostFields () {
return [
{
title: 'label.host.name',
key: 'hostName',
placeHolder: 'Please enter host name',
required: true,
display: {
hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
}
},
{
title: 'label.username',
key: 'hostUserName',
placeHolder: 'Please enter host username',
required: true,
display: {
hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
}
},
{
title: 'label.password',
key: 'hostPassword',
placeHolder: 'Please enter host password',
required: true,
password: true,
display: {
hypervisor: ['VMware', 'BareMetal', 'Ovm', 'Hyperv', 'KVM', 'XenServer', 'LXC', 'Simulator']
}
},
{
title: 'label.agent.username',
key: 'agentUserName',
placeHolder: 'Please enter Agent username',
required: false,
defaultValue: 'Oracle',
display: {
hypervisor: 'Ovm'
}
},
{
title: 'label.agent.password',
key: 'agentPassword',
placeHolder: 'Please enter Agent password',
required: true,
password: true,
display: {
hypervisor: 'Ovm'
}
},
{
title: 'label.tags',
key: 'hostTags',
placeHolder: 'Please enter host tags',
required: false
}
]
},
primaryStorageFields () {
return [
{
title: 'label.name',
key: 'primaryStorageName',
placeHolder: 'Please enter name',
required: true
},
{
title: 'label.scope',
key: 'primaryStorageScope',
required: false,
select: true,
options: this.primaryStorageScopes
},
{
title: 'label.protocol',
key: 'primaryStorageProtocol',
placeHolder: 'Please select option',
required: true,
select: true,
options: this.primaryStorageProtocols
},
{
title: 'label.server',
key: 'primaryStorageServer',
placeHolder: 'Please enter server',
required: true,
display: {
primaryStorageProtocol: ['nfs', 'iscsi', 'gluster', 'SMB']
}
},
{
title: 'label.path',
key: 'primaryStoragePath',
placeHolder: 'Please enter path',
required: true,
display: {
primaryStorageProtocol: ['nfs', 'SMB', 'SharedMountPoint', 'ocfs2']
}
},
{
title: 'label.SR.name',
key: 'primaryStorageSRLabel',
placeHolder: 'Please enter SR Name-Label',
required: true,
display: {
primaryStorageProtocol: 'PreSetup'
}
},
{
title: 'label.target.iqn',
key: 'primaryStorageTargetIQN',
placeHolder: 'Please enter Target IQN',
required: true,
display: {
primaryStorageProtocol: 'iscsi'
}
},
{
title: 'label.LUN.number',
key: 'primaryStorageLUN',
placeHolder: 'Please enter LUN #',
required: true,
display: {
primaryStorageProtocol: 'iscsi'
}
},
{
title: 'label.smb.domain',
key: 'primaryStorageSMBDomain',
placeHolder: 'Please enter SMB Domain',
required: true,
display: {
primaryStorageProtocol: 'SMB'
}
},
{
title: 'label.smb.username',
key: 'primaryStorageSMBUsername',
placeHolder: 'Please enter SMB Username',
required: true,
display: {
primaryStorageProtocol: 'SMB'
}
},
{
title: 'label.smb.password',
key: 'primaryStorageSMBPassword',
placeHolder: 'Please enter SMB Password',
required: true,
password: true,
display: {
primaryStorageProtocol: 'SMB'
}
},
{
title: 'label.rados.monitor',
key: 'primaryStorageRADOSMonitor',
placeHolder: 'Please enter RADOS Monitor',
required: false,
display: {
primaryStorageProtocol: ['rbd']
}
},
{
title: 'label.rados.pool',
key: 'primaryStorageRADOSPool',
placeHolder: 'Please enter RADOS Pool',
required: false,
display: {
primaryStorageProtocol: ['rbd']
}
},
{
title: 'label.rados.user',
key: 'primaryStorageRADOSUser',
placeHolder: 'Please enter RADOS User',
required: false,
display: {
primaryStorageProtocol: ['rbd']
}
},
{
title: 'label.rados.secret',
key: 'primaryStorageRADOSSecret',
placeHolder: 'Please enter RADOS Secret',
required: false,
display: {
primaryStorageProtocol: ['rbd']
}
},
{
title: 'label.volgroup',
key: 'primaryStorageVolumeGroup',
placeHolder: 'Please enter Volume Group',
required: true,
display: {
primaryStorageProtocol: 'clvm'
}
},
{
title: 'label.volume',
key: 'primaryStorageVolume',
placeHolder: 'Please enter Volume',
required: true,
display: {
primaryStorageProtocol: 'gluster'
}
},
{
title: 'label.vcenter.datacenter',
key: 'primaryStorageVmfsDatacenter',
placeHolder: 'Please enter vCenter Datacenter',
required: true,
display: {
primaryStorageProtocol: 'vmfs'
}
},
{
title: 'label.vcenter.datastore',
key: 'primaryStorageVmfsDatastore',
placeHolder: 'Please enter vCenter Datastore',
required: true,
display: {
primaryStorageProtocol: 'vmfs'
}
},
{
title: 'label.storage.tags',
key: 'primaryStorageTags',
placeHolder: 'Please enter storage tags',
required: false
}
]
},
secondaryStorageFields () {
return [
{
title: 'label.provider',
key: 'secondaryStorageProvider',
required: false,
select: true,
options: this.storageProviders
},
{
title: 'label.name',
key: 'secondaryStorageName',
required: false,
display: {
secondaryStorageProvider: ['NFS', 'SMB', 'S3', 'Swift']
}
},
{
title: 'label.server',
key: 'secondaryStorageServer',
required: true,
placeHolder: 'Please enter Server',
display: {
secondaryStorageProvider: ['NFS', 'SMB']
}
},
{
title: 'label.path',
key: 'secondaryStoragePath',
required: true,
placeHolder: 'Please enter Path',
display: {
secondaryStorageProvider: ['NFS', 'SMB']
}
},
{
title: 'label.smb.domain',
key: 'secondaryStorageSMBDomain',
required: true,
placeHolder: 'Please enter SMB Domain',
display: {
secondaryStorageProvider: ['SMB']
}
},
{
title: 'label.smb.username',
key: 'secondaryStorageSMBUsername',
required: true,
placeHolder: 'Please enter SMB Username',
display: {
secondaryStorageProvider: ['SMB']
}
},
{
title: 'label.smb.password',
key: 'secondaryStorageSMBPassword',
required: true,
password: true,
placeHolder: 'Please enter SMB Password',
display: {
secondaryStorageProvider: ['SMB']
}
},
{
title: 'label.s3.access_key',
key: 'secondaryStorageAccessKey',
required: true,
placeHolder: 'Please enter Access Key',
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.secret_key',
key: 'secondaryStorageSecretKey',
required: true,
placeHolder: 'Please enter Secret Key',
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.bucket',
key: 'secondaryStorageBucket',
required: true,
placeHolder: 'Please enter Bucket',
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.endpoint',
key: 'secondaryStorageEndpoint',
required: false,
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.use_https',
key: 'secondaryStorageHttps',
required: false,
switch: true,
checked: true,
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.connection_timeoutt',
key: 'secondaryStorageConnectionTimeout',
required: false,
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.max_error_retry',
key: 'secondaryStorageMaxError',
required: false,
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.socket_timeout',
key: 'secondaryStorageSocketTimeout',
required: false,
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.create.nfs.secondary.staging.storage',
key: 'secondaryStorageNFSStaging',
required: false,
switch: true,
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.nfs.server',
key: 'secondaryStorageNFSServer',
required: true,
placeHolder: 'Please enter S3 NFS Server',
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.s3.nfs.path',
key: 'secondaryStorageNFSPath',
required: true,
placeHolder: 'Please enter S3 NFS Path',
display: {
secondaryStorageProvider: ['S3']
}
},
{
title: 'label.url',
key: 'secondaryStorageURL',
required: true,
placeHolder: 'Please enter URL',
display: {
secondaryStorageProvider: ['Swift']
}
},
{
title: 'label.account',
key: 'secondaryStorageAccount',
required: false,
display: {
secondaryStorageProvider: ['Swift']
}
},
{
title: 'label.username',
key: 'secondaryStorageUsername',
required: false,
display: {
secondaryStorageProvider: ['Swift']
}
},
{
title: 'label.key',
key: 'secondaryStorageKey',
required: false,
display: {
secondaryStorageProvider: ['Swift']
}
}
]
}
},
data () {
return {
physicalNetworks: null,
currentHypervisor: null,
primaryStorageScopes: [],
primaryStorageProtocols: [],
storageProviders: [],
currentStep: 0,
options: ['primaryStorageScope', 'primaryStorageProtocol', 'provider']
}
},
mounted () {
if (this.stepChild && this.stepChild !== '') {
this.currentStep = this.steps.findIndex(item => item.fromKey === this.stepChild)
}
if (this.prefillContent.hypervisor.value === 'BareMetal') {
this.$emit('nextPressed')
} else {
this.fetchConfigurationSwitch()
this.options.forEach(this.fetchOptions)
if (!this.prefillContent.lastHypervisor) {
this.$emit('fieldsChanged', {
lastHypervisor: this.prefillContent.hypervisor
})
} else if (this.prefillContent.lastHypervisor.value !== this.prefillContent.hypervisor.value) {
this.$emit('fieldsChanged', {
lastHypervisor: this.prefillContent.hypervisor,
primaryStorageProtocol: null,
primaryStorageScope: null
})
}
}
},
methods: {
nextPressed () {
if (this.currentStep === this.steps.length - 1) {
this.$emit('nextPressed')
} else {
this.currentStep++
}
},
handleBack (e) {
if (this.currentStep === 0) {
this.$emit('backPressed')
} else {
this.currentStep--
}
},
fieldsChanged (changed) {
this.$emit('fieldsChanged', changed)
},
fetchOptions (name) {
switch (name) {
case 'primaryStorageScope':
this.fetchScope()
break
case 'primaryStorageProtocol':
this.fetchProtocol()
break
case 'provider':
this.fetchProvider()
break
default:
break
}
},
fetchScope () {
const hypervisor = this.prefillContent.hypervisor ? this.prefillContent.hypervisor.value : null
const scope = []
if (['KVM', 'VMware', 'Hyperv'].includes(hypervisor)) {
scope.push({
id: 'zone',
description: this.$t('label.zone.wide')
})
scope.push({
id: 'cluster',
description: this.$t('label.cluster')
})
} else {
scope.push({
id: 'cluster',
description: this.$t('label.cluster')
})
}
this.primaryStorageScopes = scope
this.$forceUpdate()
},
fetchProtocol () {
const hypervisor = this.prefillContent.hypervisor ? this.prefillContent.hypervisor.value : null
const protocols = []
if (hypervisor === 'KVM') {
protocols.push({
id: 'nfs',
description: 'nfs'
})
protocols.push({
id: 'SharedMountPoint',
description: 'SharedMountPoint'
})
protocols.push({
id: 'rbd',
description: 'RBD'
})
protocols.push({
id: 'clvm',
description: 'CLVM'
})
protocols.push({
id: 'gluster',
description: 'Gluster'
})
} else if (hypervisor === 'XenServer') {
protocols.push({
id: 'nfs',
description: 'nfs'
})
protocols.push({
id: 'PreSetup',
description: 'PreSetup'
})
protocols.push({
id: 'iscsi',
description: 'iscsi'
})
} else if (hypervisor === 'VMware') {
protocols.push({
id: 'nfs',
description: 'nfs'
})
protocols.push({
id: 'vmfs',
description: 'vmfs'
})
} else if (hypervisor === 'Hyperv') {
protocols.push({
id: 'SMB',
description: 'SMB/CIFS'
})
} else if (hypervisor === 'Ovm') {
protocols.push({
id: 'nfs',
description: 'nfs'
})
protocols.push({
id: 'ocfs2',
description: 'ocfs2'
})
} else if (hypervisor === 'LXC') {
protocols.push({
id: 'nfs',
description: 'nfs'
})
protocols.push({
id: 'SharedMountPoint',
description: 'SharedMountPoint'
})
protocols.push({
id: 'rbd',
description: 'RBD'
})
} else {
protocols.push({
id: 'nfs',
description: 'nfs'
})
}
this.primaryStorageProtocols = protocols
this.$forceUpdate()
},
async fetchConfigurationSwitch () {
const hypervisor = this.prefillContent.hypervisor ? this.prefillContent.hypervisor.value : null
this.$emit('fieldsChanged', { dvSwitchEnabled: { value: false } })
this.$emit('fieldsChanged', { vSwitchEnabled: { value: false } })
if (hypervisor && hypervisor === 'VMware') {
await this.fetchNexusSwitchConfig()
await this.fetchDvSwitchConfig()
}
},
fetchNexusSwitchConfig () {
api('listConfigurations', { name: 'vmware.use.nexus.vswitch' }).then(json => {
let vSwitchEnabled = false
if (json.listconfigurationsresponse.configuration[0].value) {
vSwitchEnabled = true
}
this.$emit('fieldsChanged', { vSwitchEnabled: { value: vSwitchEnabled } })
})
},
fetchDvSwitchConfig () {
let dvSwitchEnabled = false
api('listConfigurations', { name: 'vmware.use.dvswitch' }).then(json => {
if (json.listconfigurationsresponse.configuration[0].value) {
dvSwitchEnabled = true
}
this.$emit('fieldsChanged', { dvSwitchEnabled: { value: dvSwitchEnabled } })
})
},
fetchProvider () {
const storageProviders = []
api('listImageStores', { provider: 'S3' }).then(json => {
const s3stores = json.listimagestoresresponse.imagestore
if (s3stores != null && s3stores.length > 0) {
storageProviders.push({ id: 'S3', description: 'S3' })
} else {
storageProviders.push({ id: 'NFS', description: 'NFS' })
storageProviders.push({ id: 'SMB', description: 'SMB/CIFS' })
storageProviders.push({ id: 'S3', description: 'S3' })
storageProviders.push({ id: 'Swift', description: 'Swift' })
}
this.storageProviders = storageProviders
this.$forceUpdate()
})
},
submitLaunchZone () {
this.$emit('submitLaunchZone')
}
}
}
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,444 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div style="width: auto;">
<a-steps progressDot :current="currentStep" size="small" style="margin-left: 0px; margin-top: 16px;">
<a-step
v-for="step in steps"
:key="step.title"
:title="$t(step.title)"
:style="stepScales"></a-step>
</a-steps>
<zone-wizard-physical-network-setup-step
v-if="steps && steps[currentStep].formKey === 'physicalNetwork'"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:prefillContent="prefillContent"
:isFixError="isFixError"
/>
<static-inputs-form
v-if="steps && steps[currentStep].formKey === 'netscaler'"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="netscalerFields"
:prefillContent="prefillContent"
:description="netscalerSetupDescription"
:isFixError="isFixError"
/>
<ip-address-range-form
v-if="steps && steps[currentStep].formKey === 'publicTraffic'"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
traffic="public"
:description="publicTrafficDescription[zoneType.toLowerCase()]"
:prefillContent="prefillContent"
:isFixError="isFixError"
/>
<static-inputs-form
v-if="steps && steps[currentStep].formKey === 'pod'"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="podFields"
:prefillContent="prefillContent"
:description="podSetupDescription"
:isFixError="isFixError"
/>
<div v-if="guestTrafficRangeMode">
<static-inputs-form
v-if="steps && steps[currentStep].formKey === 'guestTraffic'"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="guestTrafficFields"
:prefillContent="prefillContent"
:description="guestTrafficDescription[this.zoneType.toLowerCase()]"
:isFixError="isFixError"
/>
</div>
<div v-else>
<advanced-guest-traffic-form
v-if="steps && steps[currentStep].formKey === 'guestTraffic'"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:prefillContent="prefillContent"
:description="guestTrafficDescription[this.zoneType.toLowerCase()]"
:isFixError="isFixError"
/>
</div>
<ip-address-range-form
v-if="steps && steps[currentStep].formKey === 'storageTraffic'"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
traffic="storage"
:description="storageTrafficDescription"
:prefillContent="prefillContent"
:isFixError="isFixError"
/>
</div>
</template>
<script>
import { api } from '@/api'
import ZoneWizardPhysicalNetworkSetupStep from '@views/infra/zone/ZoneWizardPhysicalNetworkSetupStep'
import IpAddressRangeForm from '@views/infra/zone/IpAddressRangeForm'
import StaticInputsForm from '@views/infra/zone/StaticInputsForm'
import AdvancedGuestTrafficForm from '@views/infra/zone/AdvancedGuestTrafficForm'
export default {
components: {
ZoneWizardPhysicalNetworkSetupStep,
IpAddressRangeForm,
StaticInputsForm,
AdvancedGuestTrafficForm
},
props: {
prefillContent: {
type: Object,
default: function () {
return {}
}
},
stepChild: {
type: String,
default: ''
},
isFixError: {
type: Boolean,
default: false
}
},
computed: {
zoneType () {
return this.prefillContent.zoneType ? this.prefillContent.zoneType.value : null
},
sgEnabled () {
return this.prefillContent.securityGroupsEnabled ? this.prefillContent.securityGroupsEnabled.value : false
},
havingNetscaler () {
return this.prefillContent.networkOfferingSelected ? this.prefillContent.networkOfferingSelected.havingNetscaler : false
},
guestTrafficRangeMode () {
return this.zoneType === 'Basic' ||
(this.zoneType === 'Advanced' && this.sgEnabled)
},
allSteps () {
const steps = []
steps.push({
title: 'label.physical.network',
formKey: 'physicalNetwork'
})
if (this.havingNetscaler) {
steps.push({
title: 'label.netScaler',
formKey: 'netscaler'
})
}
steps.push({
title: 'label.public.traffic',
formKey: 'publicTraffic',
trafficType: 'public'
})
steps.push({
title: 'label.pod',
formKey: 'pod'
})
steps.push({
title: 'label.guest.traffic',
formKey: 'guestTraffic',
trafficType: 'guest'
})
steps.push({
title: 'label.storage.traffic',
formKey: 'storageTraffic',
trafficType: 'storage'
})
return steps
},
stepScales () {
if (this.allSteps.length > 4) {
return { width: 'calc(100% / ' + this.allSteps.length + ')' }
}
return {}
},
netscalerFields () {
return [
{
title: 'label.guest.ip',
key: 'netscalerIp',
required: false,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
},
{
title: 'label.username',
key: 'netscalerUsername',
required: false
},
{
title: 'label.password',
key: 'netscalerPassword',
required: false,
password: true
},
{
title: 'label.type',
key: 'netscalerType',
required: false,
select: true,
options: this.netscalerType
},
{
title: 'label.public.interface',
key: 'publicinterface',
required: false
},
{
title: 'label.private.interface',
key: 'privateinterface',
required: false
},
{
title: 'label.gslb.service',
key: 'gslbprovider',
required: false,
switch: true
},
{
title: 'label.gslb.service.public.ip',
key: 'gslbproviderpublicip',
required: false,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
},
{
title: 'label.gslb.service.private.ip',
key: 'gslbproviderprivateip',
required: false,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
},
{
title: 'label.numretries',
key: 'numretries',
required: false
},
{
title: 'label.capacity',
key: 'capacity',
required: false
}
]
}
},
data () {
return {
physicalNetworks: null,
currentStep: 0,
steps: null,
skipGuestTrafficStep: false,
netscalerType: [],
publicTrafficDescription: {
advanced: 'message.public.traffic.in.advanced.zone',
basic: 'message.public.traffic.in.basic.zone'
},
guestTrafficDescription: {
advanced: 'message.guest.traffic.in.advanced.zone',
basic: 'message.guest.traffic.in.basic.zone'
},
podSetupDescription: 'message.add.pod.during.zone.creation',
netscalerSetupDescription: 'label.please.specify.netscaler.info',
storageTrafficDescription: 'label.zoneWizard.trafficType.management',
podFields: [
{
title: 'label.pod.name',
key: 'podName',
placeHolder: 'Please enter pod name',
required: true
},
{
title: 'label.reserved.system.gateway',
key: 'podReservedGateway',
placeHolder: 'Please enter system gateway for Pod',
required: true
},
{
title: 'label.reserved.system.netmask',
key: 'podReservedNetmask',
placeHolder: 'Please enter system netmask for Pod',
required: true
},
{
title: 'label.start.reserved.system.IP',
key: 'podReservedStartIp',
placeHolder: 'Please enter reserved system start ip for Pod',
required: true,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
},
{
title: 'label.end.reserved.system.IP',
key: 'podReservedStopIp',
placeHolder: 'Please enter reserved system stop ip for Pod',
required: false,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
}
],
guestTrafficFields: [
{
title: 'label.guest.gateway',
key: 'guestGateway',
placeHolder: 'Please enter guest gateway',
required: false
},
{
title: 'label.guest.netmask',
key: 'guestNetmask',
placeHolder: 'Please enter guest netmask',
required: false
},
{
title: 'label.guest.start.ip',
key: 'guestStartIp',
placeHolder: 'Please enter start ip for guest traffic',
required: false,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
},
{
title: 'label.guest.end.ip',
key: 'guestStopIp',
placeHolder: 'Please enter stop ip for guest traffic',
required: false,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
}
]
}
},
mounted () {
this.physicalNetworks = this.prefillContent.physicalNetworks
this.steps = this.filteredSteps()
this.currentStep = this.prefillContent.networkStep ? this.prefillContent.networkStep : 0
if (this.stepChild && this.stepChild !== '') {
this.currentStep = this.steps.findIndex(item => item.formKey === this.stepChild)
}
if (this.zoneType === 'Basic' ||
(this.zoneType === 'Advanced' && this.sgEnabled)) {
this.skipGuestTrafficStep = false
} else {
this.fetchConfiguration()
}
this.$emit('fieldsChanged', { skipGuestTrafficStep: this.skipGuestTrafficStep })
this.fetchNetscalerType()
},
methods: {
fetchNetscalerType () {
const items = []
items.push({
id: 'NetscalerMPXLoadBalancer',
description: 'NetScaler MPX LoadBalancer'
})
items.push({
id: 'NetscalerVPXLoadBalancer',
description: 'NetScaler VPX LoadBalancer'
})
items.push({
id: 'NetscalerSDXLoadBalancer',
description: 'NetScaler SDX LoadBalancer'
})
this.netscalerType = items
this.$forceUpdate()
},
nextPressed () {
if (this.currentStep === this.steps.length - 1) {
this.$emit('nextPressed')
} else {
this.currentStep++
this.$emit('fieldsChanged', { networkStep: this.currentStep })
}
},
handleBack (e) {
if (this.currentStep === 0) {
this.$emit('backPressed')
} else {
this.currentStep--
this.$emit('fieldsChanged', { networkStep: this.currentStep })
}
},
submitLaunchZone () {
this.$emit('submitLaunchZone')
},
fieldsChanged (changed) {
if (changed.physicalNetworks) {
this.physicalNetworks = changed.physicalNetworks
this.steps = this.filteredSteps()
}
this.$emit('fieldsChanged', changed)
},
filteredSteps () {
return this.allSteps.filter(step => {
if (!step.trafficType) return true
if (this.physicalNetworks) {
let neededTraffic = false
this.physicalNetworks.forEach(net => {
net.traffics.forEach(traffic => {
if (traffic.type === step.trafficType) {
neededTraffic = true
}
})
})
if (neededTraffic) return true
}
return false
})
},
fetchConfiguration () {
this.skipGuestTrafficStep = false
api('listConfigurations', { name: 'sdn.ovs.controller' }).then(json => {
const items = json.listconfigurationsresponse.configuration
items.forEach(item => {
if (item.name === 'sdn.ovs.controller') {
if (item.value) {
this.skipGuestTrafficStep = true
}
return false
}
})
})
}
}
}
</script>

View File

@ -0,0 +1,501 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div>
<a-card
class="ant-form-text"
style="text-align: justify; margin: 10px 0; padding: 20px;"
v-html="zoneType !== null ? $t(zoneDescription[zoneType]) : 'Please select zone type below.'">
</a-card>
<a-table
bordered
:dataSource="physicalNetworks"
:columns="columns"
:pagination="false"
style="margin-bottom: 24px;">
<template slot="name" slot-scope="text, record">
<a-input :value="text" @change="e => onCellChange(record.key, 'name', e.target.value)" />
</template>
<template slot="isolationMethod" slot-scope="text, record">
<a-select
style="width: 100%"
:defaultValue="text"
@change="value => onCellChange(record.key, 'isolationMethod', value)"
>
<a-select-option value="VLAN"> VLAN </a-select-option>
<a-select-option value="VXLAN"> VXLAN </a-select-option>
<a-select-option value="GRE"> GRE </a-select-option>
<a-select-option value="STT"> STT </a-select-option>
<a-select-option value="BCF_SEGMENT"> BCF_SEGMENT </a-select-option>
<a-select-option value="ODL"> ODL </a-select-option>
<a-select-option value="L3VPN"> L3VPN </a-select-option>
<a-select-option value="VSP"> VSP </a-select-option>
<a-select-option value="VCS"> VCS </a-select-option>
</a-select>
</template>
<template slot="traffics" slot-scope="traffics, record">
<div v-for="traffic in traffics" :key="traffic.type">
<a-tag
:color="trafficColors[traffic.type]"
style="margin:2px"
>
{{ traffic.type.toUpperCase() }}
<a-icon type="edit" class="traffic-type-action" @click="editTraffic(record.key, traffic, $event)"/>
<a-icon type="delete" class="traffic-type-action" @click="deleteTraffic(record.key, traffic, $event)"/>
</a-tag>
</div>
<a-modal
title="Edit traffic type"
:visible="showEditTraffic"
:closable="true"
@ok="updateTrafficLabel(trafficInEdit)"
@cancel="cancelEditTraffic"
centered
>
<a-form :form="form">
<span class="ant-form-text"> Please specify the traffic label you want associated with this traffic type. </span>
<a-form-item v-bind="formItemLayout" style="margin-top:16px;" label="Traffic Label">
<a-input
v-decorator="['trafficLabel', {
rules: [{
required: true,
message: 'Please enter traffic label',
}]
}]"
/>
</a-form-item>
</a-form>
</a-modal>
<div v-if="isShowAddTraffic(record.traffics)">
<div class="traffic-select-item" v-if="addingTrafficForKey === record.key">
<a-select
:defaultValue="trafficLabelSelected"
@change="val => { trafficLabelSelected = val }"
style="min-width: 120px;"
>
<a-select-option
v-for="(traffic, index) in availableTrafficToAdd"
:value="traffic"
:key="index"
:disabled="isDisabledTraffic(record.traffics, traffic)"
>
{{ traffic.toUpperCase() }}
</a-select-option>
</a-select>
<a-button
class="icon-button"
shape="circle"
icon="plus"
size="small"
@click="trafficAdded" />
<a-button
class="icon-button"
type="danger"
shape="circle"
icon="close"
size="small"
@click="() => { addingTrafficForKey = null }" />
</div>
<a-tag
key="addingTraffic"
style="margin:2px;"
v-else
@click="addingTraffic(record.key, record.traffics)"
>
<a-icon type="plus" />
{{ $t('label.add.traffic') }}
</a-tag>
</div>
</template>
<template slot="actions" slot-scope="text, record">
<a-button v-if="physicalNetworks.indexOf(record) > 0" type="danger" shape="circle" icon="delete" @click="onDelete(record)" />
</template>
<template slot="footer" v-if="isAdvancedZone">
<a-button
@click="handleAddPhysicalNetwork">
{{ $t('label.add.physical.network') }}
</a-button>
</template>
</a-table>
<div class="form-action">
<a-button
v-if="!isFixError"
class="button-right"
@click="handleBack">
{{ $t('label.previous') }}
</a-button>
<a-button
class="button-next"
type="primary"
@click="handleSubmit">
{{ $t('label.next') }}
</a-button>
</div>
<a-modal
:visible="showError"
title="Error!"
@ok="() => { showError = false }"
@cancel="() => { showError = false }"
centered
>
<span>{{ $t('message.required.traffic.type') }}</span>
</a-modal>
</div>
</template>
<script>
export default {
props: {
prefillContent: {
type: Object,
default: function () {
return {}
}
},
isFixError: {
type: Boolean,
default: false
}
},
data () {
return {
formItemLayout: {
labelCol: { span: 10 },
wrapperCol: { span: 12 }
},
physicalNetworks: [],
count: 0,
zoneDescription: {
Basic: 'message.setup.physical.network.during.zone.creation.basic',
Advanced: 'message.setup.physical.network.during.zone.creation'
},
hasUnusedPhysicalNetwork: false,
trafficColors: {
public: 'orange',
guest: 'green',
management: 'blue',
storage: 'red'
},
showEditTraffic: false,
trafficInEdit: null,
availableTrafficToAdd: ['storage'],
addingTrafficForKey: '-1',
trafficLabelSelected: null,
showError: false,
defaultTrafficOptions: []
}
},
computed: {
columns () {
const columns = []
columns.push({
title: this.$t('label.network.name'),
dataIndex: 'name',
width: '30%',
scopedSlots: { customRender: 'name' }
})
columns.push({
title: this.$t('label.isolation.method'),
dataIndex: 'isolationMethod',
width: '20%',
scopedSlots: { customRender: 'isolationMethod' }
})
columns.push({
title: this.$t('label.traffic.types'),
key: 'traffics',
dataIndex: 'traffics',
scopedSlots: { customRender: 'traffics' }
})
if (this.isAdvancedZone) {
columns.push({
title: '',
dataIndex: 'actions',
scopedSlots: { customRender: 'actions' },
width: 50
})
}
return columns
},
isAdvancedZone () {
return this.zoneType === 'Advanced'
},
zoneType () {
return this.prefillContent.zoneType ? this.prefillContent.zoneType.value : null
},
securityGroupsEnabled () {
return this.isAdvancedZone && (this.prefillContent.securityGroupsEnabled ? this.prefillContent.securityGroupsEnabled.value : false)
},
networkOfferingSelected () {
return this.prefillContent.networkOfferingSelected
},
needsPublicTraffic () {
if (!this.isAdvancedZone) { // Basic zone
return (this.networkOfferingSelected && (this.networkOfferingSelected.havingEIP || this.networkOfferingSelected.havingELB))
} else {
return !this.securityGroupsEnabled
}
},
requiredTrafficTypes () {
const traffics = ['management', 'guest']
if (this.needsPublicTraffic) {
traffics.push('public')
}
return traffics
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
},
created () {
this.defaultTrafficOptions = ['management', 'guest', 'storage']
if (this.isAdvancedZone || this.needsPublicTraffic) {
this.defaultTrafficOptions.push('public')
}
this.physicalNetworks = this.prefillContent.physicalNetworks
this.hasUnusedPhysicalNetwork = this.getHasUnusedPhysicalNetwork()
const requiredTrafficTypes = this.requiredTrafficTypes
if (this.physicalNetworks && this.physicalNetworks.length > 0) {
this.count = this.physicalNetworks.length
requiredTrafficTypes.forEach(type => {
let foundType = false
this.physicalNetworks.forEach(net => {
for (const index in net.traffics) {
const traffic = net.traffics[index]
if (traffic.type === 'storage') {
const idx = this.availableTrafficToAdd.indexOf(traffic.type)
if (idx > -1) this.availableTrafficToAdd.splice(idx, 1)
}
if (traffic.type === type) {
foundType = true
}
}
})
if (!foundType) this.availableTrafficToAdd.push(type)
})
} else {
const traffics = requiredTrafficTypes.map(item => {
return { type: item, label: '' }
})
this.count = 1
this.physicalNetworks = [{ key: this.randomKeyTraffic(this.count), name: 'Physical Network 1', isolationMethod: 'VLAN', traffics: traffics }]
}
if (this.isAdvancedZone) {
this.availableTrafficToAdd.push('guest')
}
this.emitPhysicalNetworks()
},
methods: {
onCellChange (key, dataIndex, value) {
const physicalNetworks = [...this.physicalNetworks]
const target = physicalNetworks.find(item => item.key === key)
if (target) {
target[dataIndex] = value
this.physicalNetworks = physicalNetworks
}
this.emitPhysicalNetworks()
},
onDelete (record) {
record.traffics.forEach(traffic => {
if (!this.availableTrafficToAdd.includes(traffic.type)) {
this.availableTrafficToAdd.push(traffic.type)
}
})
const physicalNetworks = [...this.physicalNetworks]
this.physicalNetworks = physicalNetworks.filter(item => item.key !== record.key)
this.hasUnusedPhysicalNetwork = this.getHasUnusedPhysicalNetwork()
this.emitPhysicalNetworks()
},
handleAddPhysicalNetwork () {
const { count, physicalNetworks } = this
const newData = {
key: this.randomKeyTraffic(count + 1),
name: `Physical Network ${count + 1}`,
isolationMethod: 'VLAN',
traffics: []
}
this.physicalNetworks = [...physicalNetworks, newData]
this.count = count + 1
this.hasUnusedPhysicalNetwork = this.getHasUnusedPhysicalNetwork()
},
isValidSetup () {
const shouldHaveLabels = this.physicalNetworks.length > 1
let isValid = true
this.requiredTrafficTypes.forEach(type => {
let foundType = false
this.physicalNetworks.forEach(net => {
net.traffics.forEach(traffic => {
if (traffic.type === type) {
foundType = true
}
if (shouldHaveLabels && (!traffic.label || traffic.label.length === 0)) {
isValid = false
}
})
})
if (!foundType || !isValid) {
isValid = false
}
})
return isValid
},
handleSubmit (e) {
if (this.isValidSetup()) {
if (this.isFixError) {
this.$emit('submitLaunchZone')
return
}
this.$emit('nextPressed', this.physicalNetworks)
} else {
this.showError = true
}
},
handleBack (e) {
this.$emit('backPressed')
},
addingTraffic (key, traffics) {
this.addingTrafficForKey = key
this.availableTrafficToAdd.forEach(type => {
const trafficEx = traffics.filter(traffic => traffic.type === type)
if (!trafficEx || trafficEx.length === 0) {
this.trafficLabelSelected = type
return false
}
})
},
trafficAdded (trafficType) {
const trafficKey = this.physicalNetworks.findIndex(network => network.key === this.addingTrafficForKey)
this.physicalNetworks[trafficKey].traffics.push({
type: this.trafficLabelSelected.toLowerCase(),
label: ''
})
if (!this.isAdvancedZone || this.trafficLabelSelected !== 'guest') {
this.availableTrafficToAdd = this.availableTrafficToAdd.filter(item => item !== this.trafficLabelSelected)
}
this.addingTrafficForKey = null
this.trafficLabelSelected = null
this.emitPhysicalNetworks()
},
editTraffic (key, traffic, $event) {
this.trafficInEdit = {
key: key,
traffic: traffic
}
this.showEditTraffic = true
this.form.setFieldsValue({
trafficLabel: this.trafficInEdit !== null ? this.trafficInEdit.traffic.label : null
})
},
deleteTraffic (key, traffic, $event) {
const trafficKey = this.physicalNetworks.findIndex(network => network.key === key)
this.physicalNetworks[trafficKey].traffics = this.physicalNetworks[trafficKey].traffics.filter(tr => {
return tr.type !== traffic.type
})
if (!this.isAdvancedZone || traffic.type !== 'guest') {
this.availableTrafficToAdd.push(traffic.type)
}
this.hasUnusedPhysicalNetwork = this.getHasUnusedPhysicalNetwork()
this.emitPhysicalNetworks()
},
updateTrafficLabel (trafficInEdit) {
this.form.validateFields((err, values) => {
if (!err) {
this.showEditTraffic = false
trafficInEdit.traffic.label = values.trafficLabel
this.trafficInEdit = null
}
})
this.emitPhysicalNetworks()
},
cancelEditTraffic () {
this.showEditTraffic = false
this.trafficInEdit = null
},
getHasUnusedPhysicalNetwork () {
let hasUnused = false
if (this.physicalNetworks && this.physicalNetworks.length > 0) {
this.physicalNetworks.forEach(item => {
if (!item.traffics || item.traffics.length === 0) {
hasUnused = true
}
})
}
return hasUnused
},
emitPhysicalNetworks () {
if (this.physicalNetworks) {
this.$emit('fieldsChanged', { physicalNetworks: this.physicalNetworks })
}
},
isDisabledTraffic (traffics, traffic) {
const trafficEx = traffics.filter(item => item.type === traffic)
if (trafficEx && trafficEx.length > 0) {
return true
}
return false
},
isShowAddTraffic (traffics) {
if (!this.availableTrafficToAdd || this.availableTrafficToAdd.length === 0) {
return false
}
if (traffics.length === this.defaultTrafficOptions.length) {
return false
}
if (this.isAdvancedZone && this.availableTrafficToAdd.length === 1) {
const guestEx = traffics.filter(traffic => traffic.type === 'guest')
if (guestEx && guestEx.length > 0) {
return false
}
}
return true
},
randomKeyTraffic (key) {
const now = new Date()
const random = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
return [key, random, now.getTime()].join('')
}
}
}
</script>
<style scoped lang="less">
.form-action {
margin-top: 16px;
}
.traffic-type-action {
margin-left: 2px;
margin-right: 2px;
padding-left: 1px;
padding-right: 1px;
}
.physical-network-support {
margin: 10px 0;
}
.traffic-select-item {
/deep/.icon-button {
margin: 0 0 0 5px;
}
}
</style>

View File

@ -0,0 +1,599 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div>
<a-card
class="ant-form-text"
style="text-align: justify; margin: 10px 0; padding: 24px;"
v-html="$t(description)">
</a-card>
<a-form class="form-content" :form="form" @submit="handleSubmit">
<a-form-item
:label="$t('label.name')"
v-bind="formItemLayout"
has-feedback>
<a-input
v-decorator="['name', {
rules: [{
required: true,
message: 'Please enter zone name',
initialValue: name
}]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.ipv4.dns1')"
v-bind="formItemLayout"
has-feedback>
<a-input
v-decorator="['ipv4Dns1', {
rules: [
{
required: true,
message: 'Please enter IpV4 DNS 1',
initialValue: ipv4Dns1
},
{
validator: checkIpFormat,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
}
]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.ipv4.dns2')"
v-bind="formItemLayout"
has-feedback>
<a-input
v-decorator="['ipv4Dns2', {
rules: [
{
message: 'Please enter IpV4 DNS 2',
initialValue: ipv4Dns2
},
{
validator: checkIpFormat,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
}
]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.ipv6.dns1')"
v-bind="formItemLayout"
v-if="isAdvancedZone && !securityGroupsEnabled"
has-feedback>
<a-input
v-decorator="['ipv6Dns1', {
rules: [
{
message: 'Please enter IpV6 DNS 1',
initialValue: ipv6Dns1
},
{
validator: checkIpFormat,
ipV6: true,
message: 'Please enter a valid IP v6 address.'
}
]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.ipv6.dns2')"
v-bind="formItemLayout"
v-if="isAdvancedZone && !securityGroupsEnabled"
has-feedback>
<a-input
v-decorator="['ipv6Dns2', {
rules: [
{
message: 'Please enter IpV6 DNS 2',
initialValue: ipv6Dns2
},
{
validator: checkIpFormat,
ipV6: true,
message: 'Please enter a valid IP v6 address.'
}
]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.internal.dns.1')"
v-bind="formItemLayout"
has-feedback>
<a-input
v-decorator="['internalDns1', {
rules: [
{
required: true,
message: 'Please enter Internal DNS 1',
initialValue: internalDns1
},
{
validator: checkIpFormat,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
}
]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.internal.dns.2')"
v-bind="formItemLayout"
has-feedback>
<a-input
v-decorator="['internalDns2', {
rules: [
{
message: 'Please enter Internal DNS 2',
initialValue: internalDns2
},
{
validator: checkIpFormat,
ipV4: true,
message: 'Please enter a valid IP v4 address.'
}
]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.hypervisor')"
v-bind="formItemLayout"
has-feedback>
<a-select
:loading="hypervisors === null"
showSearch
v-decorator="['hypervisor',{
rules: [{
required: true,
message: 'Please select hypervisor type',
initialValue: currentHypervisor
}]
}]"
placeholder="Please select hypervisor type"
>
<a-select-option v-for="hypervisor in hypervisors" :key="hypervisor.name">
{{ hypervisor.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
:label="$t('label.network.offering')"
v-bind="formItemLayout"
v-if="!isAdvancedZone || securityGroupsEnabled"
has-feedback>
<a-select
:loading="availableNetworkOfferings === null"
v-decorator="['networkOfferingId', {
rules: [{
message: 'Please select network offering',
initialValue: currentNetworkOfferingId
}]
}]"
placeholder="Please select network offering"
>
<a-select-option
v-for="networkOffering in availableNetworkOfferings"
:key="networkOffering.id">
{{ networkOffering.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
:label="$t('label.network.domain')"
v-bind="formItemLayout"
has-feedback>
<a-input
v-decorator="['networkDomain', {
rules: [{
message: 'Please enter Network domain',
intialValue: networkDomain
}]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.guest.cidr')"
v-bind="formItemLayout"
v-if="isAdvancedZone && !securityGroupsEnabled"
has-feedback>
<a-input
v-decorator="['guestcidraddress', {
rules: [{
intialValue: guestcidraddress
}]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.dedicated')"
v-bind="formItemLayout">
<a-switch
v-decorator="['isDedicated', { valuePropName: 'checked' }]"
:value="isDedicated"
/>
</a-form-item>
<a-form-item
:label="$t('label.domains')"
v-bind="formItemLayout"
has-feedback
v-if="isDedicated">
<a-select
:loading="domains === null"
v-decorator="['domainId', {
rules: [{
initialValue: domain
}]
}]"
placeholder="Please select domain to dedicate to"
>
<a-select-option v-for="dom in domains" :key="dom.id">
{{ dom.path }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
:label="$t('label.account')"
v-bind="formItemLayout"
v-if="isDedicated">
<a-input
v-decorator="['account', {
rules: [{
intialValue: guestcidraddress
}]
}]"
/>
</a-form-item>
<a-form-item
:label="$t('label.local.storage.enabled')"
v-bind="formItemLayout">
<a-switch
v-decorator="['localstorageenabled', { valuePropName: 'checked' }]"
:value="localstorageenabled"
/>
</a-form-item>
<a-form-item
:label="$t('label.local.storage.enabled.system.vms')"
v-bind="formItemLayout">
<a-switch
v-decorator="['localstorageenabledforsystemvm', { valuePropName: 'checked' }]"
:value="localstorageenabledforsystemvm"
/>
</a-form-item>
</a-form>
<div class="form-action">
<a-button
@click="handleBack"
class="button-back"
v-if="!isFixError">
{{ $t('label.previous') }}
</a-button>
<a-button type="primary" @click="handleSubmit" class="button-next">
{{ $t('label.next') }}
</a-button>
</div>
</div>
</template>
<script>
import { api } from '@/api'
export default {
props: {
prefillContent: {
type: Object,
default: function () {
return {}
}
},
isFixError: {
type: Boolean,
default: false
}
},
data: () => ({
description: 'message.desc.zone',
formItemLayout: {
labelCol: { span: 8 },
wrapperCol: { span: 12 }
},
hypervisors: null,
networkOfferings: null,
domains: null,
baremetalProviders: ['BaremetalDhcpProvider', 'BaremetalPxeProvider', 'BaremetalUserdataProvider'],
selectedBaremetalProviders: [],
availableNetworkOfferings: null,
ipV4Regex: /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i,
ipV6Regex: /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i
}),
created () {
this.hypervisors = this.prefillContent.hypervisors ? this.prefillContent.hypervisors : null
this.networkOfferings = this.prefillContent.networkOfferings ? this.prefillContent.networkOfferings : null
this.form = this.$form.createForm(this, {
onFieldsChange: (_, changedFields) => {
if (changedFields.networkOfferingId && this.prefillContent.networkOfferingSelected) {
if (this.prefillContent.networkOfferingSelected.id !== changedFields.networkOfferingId.value) {
changedFields.physicalNetworks = []
}
}
if (this.networkOfferings && changedFields.networkOfferingId) {
changedFields.networkOfferings = this.networkOfferings
changedFields.networkOfferingSelected = this.networkOfferings[changedFields.networkOfferingId.value]
}
if (this.hypervisors && changedFields.hypervisor) {
changedFields.hypervisors = this.hypervisors
this.availableNetworkOfferings = this.getAvailableNetworkOfferings(changedFields.hypervisor)
}
if (this.domains && changedFields.domain) {
changedFields.domains = this.domains
}
this.$emit('fieldsChanged', changedFields)
}
})
},
mounted () {
this.form.setFieldsValue({
name: this.name,
ipv4Dns1: this.ipv4Dns1,
ipv4Dns2: this.ipv4Dns2,
ipv6Dns1: this.ipv6Dns1,
ipv6Dns2: this.ipv6Dns2,
internalDns1: this.internalDns1,
internalDns2: this.internalDns2,
hypervisor: this.currentHypervisor,
networkOfferingId: this.currentNetworkOfferingId,
networkDomain: this.networkDomain,
guestcidraddress: this.isAdvancedZone && !this.securityGroupsEnabled ? this.guestcidraddress : null,
isDedicated: this.isDedicated,
domain: this.domain,
account: this.account,
localstorageenabled: this.localstorageenabled,
localstorageenabledforsystemvm: this.localstorageenabledforsystemvm
})
const cForm = this.form
api('listHypervisors', { listAll: true }).then(json => {
this.hypervisors = json.listhypervisorsresponse.hypervisor
if ('listSimulatorHAStateTransitions' in this.$store.getters.apis) {
this.hypervisors.push({ name: 'Simulator' })
}
cForm.setFieldsValue({
hypervisor: this.currentHypervisor
})
})
if (!this.isAdvancedZone || this.securityGroupsEnabled) {
api('listNetworkOfferings', { state: 'Enabled', guestiptype: 'Shared' }).then(json => {
this.networkOfferings = {}
json.listnetworkofferingsresponse.networkoffering.forEach(offering => {
this.setupNetworkOfferingAdditionalFlags(offering)
this.networkOfferings[offering.id] = offering
})
this.availableNetworkOfferings = this.getAvailableNetworkOfferings(this.currentHypervisor)
cForm.setFieldsValue({
networkOfferingId: this.currentNetworkOfferingId
})
})
}
api('listDomains', { listAll: true }).then(json => {
this.domains = {}
json.listdomainsresponse.domain.forEach(dom => {
this.domains[dom.id] = dom
})
cForm.setFieldsValue({
domain: this.domain
})
})
},
computed: {
isAdvancedZone () {
return this.zoneType === 'Advanced'
},
zoneType () {
return this.prefillContent.zoneType ? this.prefillContent.zoneType.value : null
},
securityGroupsEnabled () {
return this.isAdvancedZone && (this.prefillContent.securityGroupsEnabled ? this.prefillContent.securityGroupsEnabled.value : false)
},
name () {
return this.prefillContent.name ? this.prefillContent.name.value : null
},
ipv4Dns1 () {
return this.prefillContent.ipv4Dns1 ? this.prefillContent.ipv4Dns1.value : null
},
ipv4Dns2 () {
return this.prefillContent.ipv4Dns2 ? this.prefillContent.ipv4Dns2.value : null
},
ipv6Dns1 () {
return this.prefillContent.ipv6Dns1 ? this.prefillContent.ipv6Dns1.value : null
},
ipv6Dns2 () {
return this.prefillContent.ipv6Dns2 ? this.prefillContent.ipv6Dns2.value : null
},
internalDns1 () {
return this.prefillContent.internalDns1 ? this.prefillContent.internalDns1.value : null
},
internalDns2 () {
return this.prefillContent.internalDns2 ? this.prefillContent.internalDns2.value : null
},
currentHypervisor () {
if (this.prefillContent.hypervisor) {
return this.prefillContent.hypervisor.value
} else if (this.hypervisors && this.hypervisors.length > 0) {
return this.hypervisors[0]
}
return null
},
currentNetworkOfferingId () {
const lastNetworkOfferingId = this.prefillContent.networkOfferingSelected ? this.prefillContent.networkOfferingSelected.id : null
if (this.networkOfferings) {
if (lastNetworkOfferingId !== null && this.networkOfferings[lastNetworkOfferingId]) {
return lastNetworkOfferingId
}
return this.availableNetworkOfferings[0].id
}
return null
},
networkDomain () {
return this.prefillContent.networkDomain ? this.prefillContent.networkDomain.value : null
},
guestcidraddress () {
return this.prefillContent.guestcidraddress ? this.prefillContent.guestcidraddress.value : '10.1.1.0/24'
},
isDedicated () {
return this.prefillContent.isDedicated ? this.prefillContent.isDedicated.value : false
},
domain () {
const lastDomainId = this.prefillContent.domainId ? this.prefillContent.domainId.value : null
if (this.domains !== null && lastDomainId !== null && this.domains[lastDomainId]) {
return lastDomainId
}
return null
},
account () {
return this.prefillContent.account ? this.prefillContent.account.value : null
},
localstorageenabled () {
return this.prefillContent.localstorageenabled ? this.prefillContent.localstorageenabled.value : false
},
localstorageenabledforsystemvm () {
return this.prefillContent.localstorageenabledforsystemvm ? this.prefillContent.localstorageenabledforsystemvm.value : false
}
},
methods: {
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (err) {
return
}
if (this.isFixError) {
this.$emit('submitLaunchZone')
return
}
this.$emit('nextPressed')
})
},
handleBack (e) {
this.$emit('backPressed')
},
setupNetworkOfferingAdditionalFlags (nOffering) {
nOffering.havingNetscaler = false
nOffering.havingSG = false
nOffering.havingEIP = false
nOffering.havingELB = false
nOffering.selectedBaremetalProviders = []
nOffering.service.forEach(service => {
service.provider.forEach(provider => {
if (provider.name === 'Netscaler') {
nOffering.havingNetscaler = true
} else if (this.baremetalProviders.includes(provider.name)) {
this.selectedBaremetalProviders.push(this.name)
nOffering.selectedBaremetalProviders = this.selectedBaremetalProviders
}
})
if (service.name === 'SecurityGroup') {
nOffering.havingSG = true
} else if (service.name === 'StaticNat') {
service.capability.forEach(capability => {
if (capability.name === 'ElasticIp' && capability.value === 'true') {
nOffering.havingEIP = true
}
})
} else if (service.name === 'Lb') {
service.capability.forEach(capability => {
if (capability.name === 'ElasticLb' && capability.value === 'true') {
nOffering.havingELB = true
}
})
}
})
},
getAvailableNetworkOfferings (hypervisor) {
if (this.networkOfferings) {
return Object.values(this.networkOfferings).filter(nOffering => {
if ((hypervisor === 'VMware' ||
(this.isAdvancedZone && this.securityGroupsEnabled)) &&
(nOffering.havingEIP && nOffering.havingELB)) {
return false
}
if (this.isAdvancedZone && this.securityGroupsEnabled && !nOffering.havingSG) {
return false
}
return true
})
}
return null
},
checkIpFormat (rule, value, callback) {
if (!value || value === '') {
callback()
} else if (rule.ipV4 && !this.ipV4Regex.test(value)) {
callback(rule.message)
} else if (rule.ipV6 && !this.ipV6Regex.test(value)) {
callback(rule.message)
} else {
callback()
}
}
}
}
</script>
<style scoped lang="less">
.form-content {
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
vertical-align: center;
padding: 8px;
padding-top: 16px;
margin-top: 8px;
max-height: 40vh;
overflow-y: auto;
/deep/.has-error {
.ant-form-explain {
text-align: left;
}
}
/deep/.ant-form-item-control {
text-align: left;
}
}
</style>

View File

@ -0,0 +1,153 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div>
<a-form
class="form-content"
:form="form"
@submit="handleSubmit">
<a-form-item>
<a-radio-group
v-decorator="['zoneType', {
rules: [{
required: true,
message: 'Please select zone type',
initialValue: zoneType
}]
}]">
<a-card :gutter="12" class="card-item">
<a-col :md="6" :lg="6">
<a-radio class="card-form-item" value="Advanced">{{ $t('label.advanced') }}</a-radio>
</a-col>
<a-col :md="18" :lg="18">
<a-card class="ant-form-text zone-support">{{ $t(zoneDescription.Advanced) }}</a-card>
</a-col>
<a-col :md="6" :lg="6" style="margin-top: 15px">
<a-form-item
class="card-form-item"
v-bind="formItemLayout">
<a-switch
class="card-form-item"
v-decorator="['securityGroupsEnabled', { valuePropName: 'checked' }]"
:value="securityGroupsEnabled"
:disabled="!isAdvancedZone"
/>
</a-form-item>
<span>{{ $t('label.menu.security.groups') }}</span>
</a-col>
<a-col :md="18" :lg="18" style="margin-top: 15px;">
<a-card class="zone-support">{{ $t(zoneDescription.SecurityGroups) }}</a-card>
</a-col>
</a-card>
</a-radio-group>
</a-form-item>
</a-form>
<div class="form-action">
<a-button type="primary" @click="handleSubmit" class="button-next">
{{ $t('label.next') }}
</a-button>
</div>
</div>
</template>
<script>
export default {
props: {
prefillContent: {
type: Object,
default: function () {
return {}
}
}
},
data: () => ({
formItemLayout: {
labelCol: { span: 6 },
wrapperCol: { span: 14 }
},
zoneDescription: {
Advanced: 'message.desc.advanced.zone',
SecurityGroups: 'message.advanced.security.group'
}
}),
beforeCreate () {
this.form = this.$form.createForm(this, {
onFieldsChange: (_, changedFields) => {
this.$emit('fieldsChanged', changedFields)
}
})
},
mounted () {
this.form.setFieldsValue({
zoneType: this.zoneType,
securityGroupsEnabled: this.securityGroupsEnabled
})
},
computed: {
isAdvancedZone () {
return this.zoneType === 'Advanced'
},
zoneType () {
return this.prefillContent.zoneType ? this.prefillContent.zoneType.value : 'Advanced'
},
securityGroupsEnabled () {
return this.isAdvancedZone && (this.prefillContent.securityGroupsEnabled ? this.prefillContent.securityGroupsEnabled.value : false)
}
},
methods: {
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
this.$emit('nextPressed')
}
})
}
}
}
</script>
<style scoped lang="less">
.form-content {
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
vertical-align: center;
padding: 8px;
padding-top: 16px;
margin-top: 8px;
}
.card-item {
margin-top: 10px;
.card-form-item {
float: left;
}
.checkbox-advance {
margin-top: 10px;
}
.zone-support {
text-align: justify;
background: #fafafa;
}
}
</style>

View File

@ -114,7 +114,8 @@ module.exports = {
target: process.env.CS_URL || 'http://localhost:8080',
secure: false,
ws: false,
changeOrigin: true
changeOrigin: true,
proxyTimeout: 10 * 60 * 1000 // 10 minutes
}
},
https: process.env.HTTPS_KEY ? {